rtk rewrite mishandles newline-separated (multi-line) commands → only first line rewritten / empty output
#2,191 opened on Jun 1, 2026
Description
Summary
rtk rewrite does not treat newline (\n) as a statement separator. It appears to only parse/rewrite the first newline-delimited segment of a command, which breaks multi-statement commands written across multiple lines:
- If the first line is not a rewritable command (
cd,echo, …),rtk rewritereturns empty output with exit 1 ("no rewrite"), even when later lines contain rewritable commands (grep,tsc, …). - If the first line is rewritable, only that first line is rewritten; rewritable commands on subsequent lines are left untouched.
Semicolon-separated equivalents are handled correctly. So the bug is specifically newline handling.
Combined with the official Claude Code hook (rtk-rewrite.sh, which applies the rewrite when rtk rewrite exits 0/3), this surfaces as silently truncated output when an agent runs a multi-line shell snippet containing several greps — only the first section is produced. This was hit repeatedly in a real Claude Code session.
Environment
- rtk 0.42.0 (Homebrew, macOS arm64) — also reproduced on 0.35.0
- Claude Code PreToolUse hook
rtk-rewrite.sh(# rtk-hook-version: 3)
Reproduction (deterministic, no Claude Code needed)
printf 'x\n' > /tmp/f.txt
# (1) Semicolon-separated — CORRECT (both greps rewritten):
rtk rewrite 'cd /tmp; grep -c x /tmp/f.txt; grep -c x /tmp/f.txt'
# -> cd /tmp; rtk grep -c x /tmp/f.txt; rtk grep -c x /tmp/f.txt (exit 0)
# (2) Same commands, newline-separated, first line is `cd` — BUG: empty + exit 1
rtk rewrite "$(printf 'cd /tmp\ngrep -c x /tmp/f.txt\ngrep -c x /tmp/f.txt')"
# -> (empty) (exit 1)
# (3) Newline-separated, first line is grep — BUG: only FIRST line rewritten
rtk rewrite "$(printf 'grep -c x /tmp/f.txt\ngrep -c x /tmp/f.txt')"
# -> rtk grep -c x /tmp/f.txt
# grep -c x /tmp/f.txt <- second grep left un-rewritten (exit 0)
Bisection — every multiline form whose first line is non-rewritable returns exit=1, len=0:
| command | result |
|---|---|
cd /tmp; grep…; grep… (semicolons) |
exit 0, full rewrite ✓ |
grep…\ngrep… (newlines, grep first) |
exit 0, only 1st line rewritten |
cd /tmp\ngrep…\ngrep… |
exit 1, empty |
cd /tmp\necho A\ngrep…\necho B\ngrep… |
exit 1, empty |
echo 中文\ngrep…\ngrep… |
exit 1, empty |
cd "/tmp/a b"\ngrep…\ngrep… |
exit 1, empty |
Expected
rtk rewrite should treat \n as a statement separator (like ;), rewriting each rewritable statement independently and preserving every statement in the output — so a newline-separated command yields the same result as its semicolon-separated equivalent.
Impact
Multi-line shell snippets are extremely common in agent/hook usage (heredocs, multi-step verification blocks). Today they are either:
- left with only the first statement token-optimized (mild), or
- effectively pass-through/empty depending on the first line (and, via hooks that act on the rewrite, produce truncated/partial command execution with no error) — silent and easy to mistake for a tool/agent bug.
Suggested fix
In the rewrite parser (registry), split the command on top-level statement separators including newline (\n) in addition to ; / && / ||, rewrite each segment, and re-join preserving the original separators.