[BUG] Bash tool normalizeToolInput silently corrupts commands: unanchored replace strips `cd <cwd> && ` even inside quoted strings

Open 💬 0 comments Opened Jul 9, 2026 by eisenferik

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

  1. Start Claude Code in any directory, e.g. /home/user/project.
  2. Have the model run this Bash tool command (or observe any command containing the literal sequence):

``bash
echo 'cd /home/user/project && ls'
``

  1. 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.replace with 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 assistant tool_use block, so the original command is unrecoverable and the corruption is invisible in logs — it looks like the model simply never wrote the cd.

Environment

  • Claude Code v2.1.205 (native installer)
  • Linux (WSL2, Ubuntu), bash

View original on GitHub ↗