#3764·rtk

hook prefixes `rtk` to a bare variable-assignment line in a multi-line command, breaking the assignment (exec fails, variable left unset)

Author: maxim-belkinCreated Aug 29, 2026Updated Sep 16, 2026

Summary

In a multi-line Bash command, the hook rewrites a plain shell variable assignment line into an rtk invocation when the assigned value is a path whose last segment matches a known handler name (observed with jq):

J=/tmp/jq   ->   rtk J=/tmp/jq

At runtime rtk then tries to exec J=/tmp/jq as a program, prints [rtk: No such file or directory (os error 2)], exits 127, and -- because the line was consumed by rtk instead of the shell -- the variable is never set. Every later $J in the script expands empty, so a path like $J/file silently becomes /file.

Version

rtk 0.46.0

Repro

$ rtk hook check "$(printf 'J=/tmp/jq\nls -l $J/f')"
rtk J=/tmp/jq
rtk ls -l $J/f

Same result through the Claude Code hook path:

$ printf '%s' '{"session_id":"t","tool_name":"Bash","tool_input":{"command":"J=/tmp/jq\nls -l $J/f"},"permission_mode":"default","cwd":"/tmp"}' \
    | rtk hook claude | jq -r '.hookSpecificOutput.updatedInput.command'
rtk J=/tmp/jq
rtk ls -l $J/f

Expected: the assignment line is left untouched (only ls should gain the rtk prefix).

Trigger bisect -- the value must be a multi-segment path whose final segment is a handler name; the variable name does not matter:

assignment line result
J=/tmp/jq mangled
K=/tmp/jq mangled
J=~/x/jq mangled
J=jq (no slash) ok
J=/tmp/jq/x ok
J=/tmp/git ok
J=/tmp/grep ok

Not every handler name triggers (git, grep, docker, kubectl, cargo, npm are safe; jq and jira are not), so it looks specific to how certain handlers match the command word.

Single-line forms are all handled correctly (J=/tmp/jq; ls $J/f, FOO=1 git status) -- only the line-by-line multi-line path is affected.

Impact

This is a data-loss class bug, similar in spirit to #3262. Real-world shape: a copy-then-cleanup script

DEST=<path ending in a trigger segment>
cp draft.md $DEST/final.md
...
rm -rf <scratch copies>

has its assignment eaten, every cp writes to / (Permission denied), and the trailing rm -rf still runs, deleting the originals before any copy landed. Claude Code executes hook-rewritten commands in an errexit-suppressed shell, so even a set -e at the top of the script cannot stop it.

Suggested fix

Never rewrite a line that parses as a shell variable assignment (NAME=value ... with no command word before it); the existing single-line code path already gets this right.