rtk-ai / rtk-ai/rtk

bug(hook): rewrite edits inside quoted string literals — 'shellcheck' in a single-quoted sed replacement became 'rtk shellcheck' (data corruption)

Open
#3,262 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug priority:critical
Dominant language
Rust
Stars
81.1k
Forks
5.1k
Avg merge
4d 21h
Merged PRs (30d)
35

Description

Summary

The Claude PreToolUse rewrite path rewrites command names inside quoted string literals. A single-quoted string assigned to a variable had its contents edited: shellcheck became rtk shellcheck inside the string, so the corrupted text — not a command — was written into files by the surrounding sed.

Like #3197, this is a corruption bug, not a filtering one: the rewritten string is not equivalent to the input, and the data the command operates on is silently altered.

Reproduction (rtk 0.43.0, macOS)

python3 - <<'EOF' > /tmp/rtk-probe.json
import json
cmd = """D='# shellcheck disable=SC2034  # comment'; for f in hooks/*.sh; do sed -i '' -e "s|^TS_BACKUP=|${D}\\nTS_BACKUP=|" "$f"; done"""
print(json.dumps({"session_id":"t","transcript_path":"/tmp/x","cwd":"/tmp",
  "hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":cmd}}))
EOF
rtk hook claude < /tmp/rtk-probe.json

Actual (updatedInput.command, abbreviated):

D='# rtk shellcheck disable=SC2034  # comment'; for f in hooks/*.sh; do ...

The token shellcheck sits inside a single-quoted literal — it is data, not a command — yet it was rewritten. Expected: passthrough (or at minimum, no edits inside quote spans).

A second probe shows the same machinery inserting a space inside a sed script:

printf '%s' '{"tool_input":{"command":"D=# shellcheck disable=SC2034; sed -e s|a|$D| f.sh"}}' | rtk hook claude
# → "command":"D=# rtk shellcheck disable=SC2034; sed -e s |a|$D| f.sh"
#                                                        ^ space inserted: `s|a|$D|` split at the `|`

s|a|$D| is one sed argument; after the rewrite it becomes s + a pipeline, which is a different program.

Real-world incident

An agent session ran a loop inserting a shellcheck directive into eight hook scripts:

D='# shellcheck disable=SC2034  # ...' && for f in hooks/*.sh; do sed -i '' -e "s|^TS_BACKUP=...|${D}\n...|" "$f"; done

All eight files ended up containing # rtk shellcheck disable=SC2034 — which shellcheck does not recognize as a directive, so the disable was silently inert. From the model's side nothing failed: exit 0, files written, corruption discovered only by re-running shellcheck. This is the worst failure mode — no error, wrong bytes on disk.

Analysis

Argument-position occurrences are not rewritten (echo 'run shellcheck later' passes through untouched), so this is not a blind substring replace. The lexer appears to lose the quote span when the opening quote is attached to an assignment word (D='...), leaving the following words in what it considers command position. The rejoin step then also normalizes spacing (the s|a|$D|s |a|$D| split above), which is the same "rejoin loses original bytes" class as #3197's suggested fix — splicing rewritten segments back by byte offset and leaving all untouched bytes verbatim would fix both.

Related

  • #3197 — ;;; ; (same corruption class, different lexer gap)
  • #1560 — pipes rewritten on the left-hand side
  • #3111 (closed) — commands with double-quoted strings were previously skipped; the current behavior rewrites them, which trades a missed optimization for corruption
  • #3171 — safe pipeline rewriting tracking issue

None cover quoted-literal contents.

Environment

  • rtk 0.43.0 (Homebrew), also checked the v0.44.0 changelog — no lexer/quoting fix listed
  • Claude Code, hook rtk hook claude, PreToolUse / matcher Bash
  • macOS (Darwin 25.5.0), bash

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the rtk hook claude PreToolUse rewrite path and reproduce the quoted-assignment and sed-script probes from the issue. Trace how quote spans and rewritten segments are lexed and rejoined; done means quoted literal contents and the s|a|$D| argument remain byte-for-byte unchanged while valid command rewriting still works.

Written by the indexing model from the issue text.

Assessment

Tech stack
bash, rust
Domain
cli, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.