rtk-ai/rtk

Exit 3 `updatedInput` mutations silently dropped in permissive Claude Code Bash modes

Open

#1233 aperta il 11 apr 2026

Vedi su GitHub
 (1 commento) (3 reazioni) (0 assegnatari)Rust (2914 fork)batch import
P0area:clibugeffort-mediumhelp wantedplatform:macospriority:high

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)

  1. 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.
  2. 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.)
  3. 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`

Guida contributor