bug(hook): rewrite edits inside quoted string literals — 'shellcheck' in a single-quoted sed replacement became 'rtk shellcheck' (data corruption)
Nobody has claimed this yet.
- 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/ matcherBash - macOS (Darwin 25.5.0), bash
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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