Exit 3 `updatedInput` mutations silently dropped in permissive Claude Code Bash modes
#1233 aperta il 11 apr 2026
Metriche repository
- Star
- (48.085 star)
- Metriche merge PR
- (Merge medio 11g 1h) (45 PR mergiate in 30 g)
Descrizione
Summary
When `rtk rewrite` returns exit code 3 (ask rule), the Claude Code hook emits `hookSpecificOutput.updatedInput` without a `permissionDecision`, relying on Claude Code to prompt the user for approval on the mutated command. In practice, when Claude Code is running in a permissive Bash mode (no prompt shown), the mutation is silently dropped and the original unrewritten command executes.
Net result: any command that rtk classifies as "ask" produces zero savings in agentic/automated Claude Code sessions — the common case for hands-off use of the hook.
Empirical evidence
Session JSONL at `~/.claude/projects//.jsonl` records `tool_use` entries whose `tool_input.command` is the pre-hook original text. The corresponding `tool_result` content format matches raw command output, not rtk's compact rewritten output.
Input command (as logged):
``` ls -la /Users/folahan/.claude/hooks/rtk-rewrite.sh 2>&1; echo "---"; /opt/homebrew/bin/rtk verify 2>&1; echo "exit=$?" ```
Tool result (as logged):
``` -rwxr-xr-x 1 folahan staff 3147 Apr 11 01:45 /Users/folahan/.claude/hooks/rtk-rewrite.sh
PASS hook integrity verified ... ```
That tool_result shows raw `ls -la` format (permissions, owner, group, size, date). Compare to what `rtk ls -la` produces for the same file:
``` rtk-rewrite.sh 3.1K ```
The raw format proves the mutation was not applied — even though `rtk rewrite` on the same command returns `rtk ls -la ...` with exit code 3, meaning the hook wanted to rewrite it.
Protocol analysis
The relevant branch in `rtk-rewrite.sh`:
```bash if [ "$EXIT_CODE" -eq 3 ]; then
Ask: rewrite the command, omit permissionDecision so Claude Code prompts.
jq -n \ --argjson updated "$UPDATED_INPUT" \ '{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "updatedInput": $updated } }' fi ```
This relies on Claude Code to (a) apply `updatedInput` and (b) surface a prompt for the mutated command. In a session where Bash is auto-approved (permission rules, acceptEdits mode, SDK/agentic usage), no prompt is shown and empirically the original command runs. Claude Code appears to skip the mutation when it can't prompt.
Suggested fix (one of)
- Fix issue "Read-only commands should return exit 0" — if `ls`/`grep`/`find`/`wc` are exit 0, they bypass this problem entirely. Covers 80% of the hot path.
- Return `permissionDecision: "ask"` explicitly so Claude Code is forced to surface a prompt rather than silently dropping. (Requires verifying that Claude Code's hook protocol honors this.)
- Detect non-interactive environments inside the hook (e.g. `[ ! -t 0 ]` or a `CLAUDE_CODE_NON_INTERACTIVE` env var) and fall back to exit-0 behavior when no TTY is present. Most defensive — RTK has no visibility into Claude Code's permission mode, so checking for an interactive session is the only portable way.
Impact
With exit 3 silently dropping mutations, the hook provides ~0% effective rewrites for any Claude Code session running in permissive Bash mode. The savings `rtk gain` reports appear to come from direct `rtk` invocations (commands typed as `rtk verify`, `rtk ls`, `rtk rewrite` literally), not from hook-mediated rewrites of bare `ls`/`grep`/`find`.
Environment
- rtk: 0.23.0+ (`rtk verify`: 141/141 tests pass)
- Claude Code: current (Opus 4.6, 1M context)
- Platform: macOS Darwin 25.5.0
- Hook: `rtk-hook-version: 3`