Summary
The rtk hook claude PreToolUse rewriter correctly handles top-level commands and common compound forms (|, ;, &&) but bypasses three runtime-resolved patterns. Result: commands that should be rewritten ship as bare invocations, leaking token savings.
Reproduction
Each test uses echo '<json>' | rtk hook claude to simulate the Claude Code PreToolUse hook input.
Works: top-level + | + ; + &&
$ echo '{"tool_input":{"command":"grep -nE pat file"}}' | rtk hook claude
{"hookSpecificOutput":{...,"updatedInput":{"command":"rtk grep -nE pat file"}}}
$ echo '{"tool_input":{"command":"grep -nE pat file | head -3"}}' | rtk hook claude
{"hookSpecificOutput":{...,"updatedInput":{"command":"rtk grep -nE pat file | head -3"}}}
$ echo '{"tool_input":{"command":"grep -nE pat1 f1; rtk grep -nE pat2 f2"}}' | rtk hook claude
{"hookSpecificOutput":{...,"updatedInput":{"command":"rtk grep -nE pat1 f1; rtk grep -nE pat2 f2"}}}
$ echo '{"tool_input":{"command":"grep -nE pat file && echo ok"}}' | rtk hook claude
{"hookSpecificOutput":{...,"updatedInput":{"command":"rtk grep -nE pat file && echo ok"}}}
Bypass 1: `$(...)` command substitution
$ echo '{"tool_input":{"command":"echo \$(grep -nE pat file)"}}' | rtk hook claude
(empty - no rewrite emitted)
Bypass 2: backticks
$ echo '{"tool_input":{"command":"echo \`grep -nE pat file\`"}}' | rtk hook claude
(empty - no rewrite emitted)
This bites real workflows like `git commit -m "$(cat <<EOF ... EOF)"` if anything inside the heredoc shells out, and `var=$(grep ...)` assignments.
Bypass 3: `xargs ` indirection
$ echo '{"tool_input":{"command":"ls *.js | xargs grep -nE pat"}}' | rtk hook claude
{"hookSpecificOutput":{...,"updatedInput":{"command":"rtk ls *.js | xargs grep -nE pat"}}}
The outer `ls` rewrites; the `grep` invoked via `xargs` stays bare because xargs resolves the command at runtime, not parse time. Same surface applies to `find ... -exec `, `bash -c ""`, `eval`, and `parallel`.
Impact
`rtk discover` over 9 sessions / 319 commands surfaced 75 missed `grep -nE` invocations totaling ~10.9K tokens. A meaningful fraction trace to these three bypass patterns (the rest are pre-hook-install historical sessions). For `git commit -m "$(...)"` users the `$()` leak alone is constant.
Suggested fixes (pick whichever the parser comfortably supports)
- Recursive parser: walk into `$(...)` and backticks and rewrite the inner command tree. The shell grammar makes this tractable - both forms have unambiguous delimiters.
- Indirect-exec detection: when the parser sees `xargs`, `find -exec`, `bash -c`, `eval`, or `parallel`, peek at the next argv token and rewrite it if it's a known RTK-handled cmd.
- Shell-side aliases: ship an opt-in `rtk shim install` that drops PATH-shadowing wrappers for the rewritable cmd set into `~/.rtk/bin`. Narrows the leak surface to `command grep` / absolute-path invocations. Cheap to add as a complement to the parser fixes.
Environment
- rtk 0.42.0 (homebrew)
- macOS Darwin 25.2.0
- Claude Code (latest)
- Hook: PreToolUse / matcher `Bash` / command `rtk hook claude`
Happy to test a fix against my workflow if a branch is published.
Summary
The
rtk hook claudePreToolUse rewriter correctly handles top-level commands and common compound forms (|,;,&&) but bypasses three runtime-resolved patterns. Result: commands that should be rewritten ship as bare invocations, leaking token savings.Reproduction
Each test uses
echo '<json>' | rtk hook claudeto simulate the Claude Code PreToolUse hook input.Works: top-level +
|+;+&&Bypass 1: `$(...)` command substitution
Bypass 2: backticks
This bites real workflows like `git commit -m "$(cat <<EOF ... EOF)"` if anything inside the heredoc shells out, and `var=$(grep ...)` assignments.
Bypass 3: `xargs ` indirection
The outer `ls` rewrites; the `grep` invoked via `xargs` stays bare because xargs resolves the command at runtime, not parse time. Same surface applies to `find ... -exec `, `bash -c ""`, `eval`, and `parallel`.
Impact
`rtk discover` over 9 sessions / 319 commands surfaced 75 missed `grep -nE` invocations totaling ~10.9K tokens. A meaningful fraction trace to these three bypass patterns (the rest are pre-hook-install historical sessions). For `git commit -m "$(...)"` users the `$()` leak alone is constant.
Suggested fixes (pick whichever the parser comfortably supports)
Environment
Happy to test a fix against my workflow if a branch is published.