[BUG] Bash tool normalizeToolInput silently corrupts commands: unanchored replace strips `cd <cwd> && ` even inside quoted strings
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The Bash tool's normalizeToolInput strips cd <cwd> && from the command with a plain, unanchored String.replace:
// from the compiled binary (v2.1.205), deobfuscated:
let { command, timeout, description } = parsedInput;
let cwd = getCwd();
let normalized = command.replace(`cd ${cwd} && `, "");
Because the pattern is a string (not an anchored check like startsWith), it matches anywhere in the command — including inside single-quoted string literals where the text is data, not a cd invocation. The command is silently corrupted before execution, and the session JSONL records the post-strip command, so there is no audit trail of what the model originally emitted.
Related: #62584 reported the same code path from the hooks/audit angle and was closed as stale with "open a new issue if this is still relevant". This report is about a different, arguably worse symptom: silent corruption of quoted data.
How it bit in practice
With cwd = /home/user/project, the model tried to register a cron job:
( crontab -l; echo '15 4 * * * cd /home/user/project && sh scripts/backup.sh >> backups/backup.log 2>&1' ) | crontab -
What actually got installed:
15 4 * * * sh scripts/backup.sh >> backups/backup.log 2>&1
The cd /home/user/project && inside the single-quoted crontab line was silently removed — producing a cron entry that fails at 04:15 with no indication anything was altered. The model retried three times with identical results (naturally — it kept emitting the same correct command). Backup jobs, deploy one-liners for other hosts, README snippets echoed into files, sh -c '...' wrappers: anything that legitimately contains the literal text cd <cwd> && as data is affected.
Steps to Reproduce
- Start Claude Code in any directory, e.g.
/home/user/project. - Have the model run this Bash tool command (or observe any command containing the literal sequence):
``bash``
echo 'cd /home/user/project && ls'
- Observe output.
Expected Behavior
cd /home/user/project && ls
The quoted string is data; nothing should be removed. Stripping a redundant cd prefix should only apply to an actual prefix, e.g.:
const prefix = `cd ${cwd} && `;
const normalized = command.startsWith(prefix) ? command.slice(prefix.length) : command;
Actual Behavior
ls
Further characterization (all verified on v2.1.205, Linux/WSL2):
- Only the first occurrence is stripped (JS
String.replacewith a string pattern), so a command containing the sequence twice keeps the second one — making the corruption look nondeterministic. - The trailing space matters:
...cd <cwd> &&'at end of string survives;cd <cwd> && <anything>is stripped. - Other directories and
;-separated forms are unaffected — it is exactly the current cwd plus `&&`. - The session transcript (
~/.claude/projects/.../<session>.jsonl) stores the post-strip command in the assistanttool_useblock, so the original command is unrecoverable and the corruption is invisible in logs — it looks like the model simply never wrote thecd.
Environment
- Claude Code v2.1.205 (native installer)
- Linux (WSL2, Ubuntu), bash