[BUG] Windows Git Bash: static analysis false-positives on read-only cd-compound commands cause constant, unsuppressable permission prompts (since 2.1.232 / auto-mode rollout)
Status Fixed / completed
Reported on v2.1.232
Maintainer reply None cached
Workaround ✓ Mentioned in thread ↓
Activity 10 comments · opened Aug 14, 2026 · closed Aug 20, 2026
Environment
- Claude Code 2.1.232 (native install), Windows 11 Pro, Git Bash shell
- Observed independently on two separate machines (different users, different hardware, same day)
- Onset correlates exactly with 2.1.232 arriving via the native launcher's startup self-update (version files on disk: 2.1.229 → 13 Aug 08:30, 2.1.231 → 13 Aug 09:41, 2.1.232 → 14 Aug 06:01 local; prompts began within hours on both machines), compounding with the 14 Aug auto-mode default rollout.
What happens
On Windows with Git Bash, any Bash command of the shape cd "<absolute path>" && <anything not fully literal> now fails static analysis and falls through to a manual "Do you want to proceed?" prompt. We have collected four distinct reason wordings verbatim:
- > Contains simple_expansion On Windows with Git Bash, the final working directory of this cd-compound cannot be statically determined, so relative write targets cannot be checked for Cygwin-emulated symlinks and this request cannot be delegated to the auto-approval classifier.
- > Redirect target contains $(cmd) output — path is runtime-determined On Windows with Git Bash, [same continuation]
- > Contains brace with quote character (expansion obfuscation) On Windows with Git Bash, [same continuation]
- > Compound command contains cd with write operation - manual approval required to prevent path resolution bypass On Windows with Git Bash, [same continuation]
The clearest false positive
Reason 4 above was produced by this command, which contains no write operation of any kind — two sed -n reads and a pipe:
cd "C:/Users/<user>/<project>" && sed -n '/pattern-a/,/pattern-b/p' static/app.js | sed -n 1,60p
Other innocuous shapes reliably prompting:
# read-only loop → "Contains simple_expansion"
cd "C:/Users/<user>/<project>" && for v in 1.60.0 1.58.0; do echo "== $v"; uv run --with "pkg==$v" python -c "..." 2>&1 | tail -2; done
# write to a FULLY ABSOLUTE literal temp path via variable → "Redirect target contains $(cmd) output"
cd "C:/Users/<user>/<project>" && S="C:/Users/<user>/AppData/Local/Temp/claude/<session>/scratchpad" && tool > "$S/out.json" 2>&1
# heredoc script to an absolute literal path → "expansion obfuscation"
cd "C:/Users/<user>/<project>" && cat > "C:/.../scratchpad/probe.py" <<'PY'
...
PY
Note the cd target is always a quoted absolute path, so the "final working directory cannot be statically determined" claim is itself questionable for these commands.
Why the friction is severe
- Subagents and workflows on Windows generate
cd "<abs>" && ...compounds constantly (it is the natural way to scope a command to a project). Users are seeing dozens of prompts per hour during normal agentic work. - These prompts never offer "don't ask again" — presumably because a rule can't be derived from a command the analyser couldn't parse — so the friction cannot be reduced from the prompt itself. Hand-written
Bash(cmd *)allow rules in settings help, but only apply to sessions started after they are saved. - Because this lands together with the auto-mode default rollout, every Windows + Git Bash user receives the behaviour change at once, with no version pinning available on the native installer (it self-updates at launch even with
"autoUpdates": false). - Related existing reports: #43713 (expansions defeat auto-allow), #46783 (Windows "always allow" writes backslash rules that never match), #86478 / #86451 (auto-mode rollout overriding chosen permission modes).
Expected behaviour
- A command whose parts are all read-only (
sed -n,grep,head,tail, pipes, no redirects) should pass static analysis regardless ofcd, or at minimum remain delegable to the auto-approval classifier. - A command containing no redirect and no write verb should never be described to the user as containing a "write operation".
cdto a quoted absolute path should establish the working directory for the analysis of the rest of the compound.- Where the analyser can identify the offending sub-part (it already prints "The following part requires approval: …"), the prompt should offer a scoped "don't ask again".
Happy to provide more captures — we are logging every distinct wording.
Showing cached comments. Read the full discussion on GitHub ↗
9 Comments
Two additions from continued logging on the same environment (Windows 11, Git Bash, 2.1.232, auto mode):
A fifth reason wording
Produced by a workflow clearing a stale
index.lockand making a WIP commit inside a git worktree:This caution class is at least defensible (it is state-changing), but note the same boilerplate continuation is appended regardless of fit. Side observation: the stale lock itself was plausibly left by an earlier git command interrupted by one of these prompts, so the friction is starting to generate its own cleanup work.
The prompt garbles the command it quotes
A read-only pipeline (
grep/sed/echo, no writes) was prompted, and the dialog's own echo of the command replaced a pipe with a comma. Sent:Rendered in the approval dialog:
(
| cutbecame, cut.) If the analysis path sees what the dialog shows, the analyser is judging a corrupted parse of the command, which would explain some of the misclassifications above (e.g. read-only pipelines labelled as containing write operations).Continued logging, same environment (Windows 11, Git Bash, 2.1.232, auto mode). Now at seven distinct reason wordings in ~12 hours across two machines. Two new ones, both verifiably false positives:
Wording 6: Windows 8.3 short paths read as bash tilde expansion
Produced by:
The "tilde" is
ABCDEF~1— DOS 8.3 short-name notation inside a double-quoted literal. Bash performs no tilde expansion there (quoted, and not path-leading); the analyser is misreading a Windows filename convention. Since%TEMP%commonly expands to an 8.3-form path on Windows, any command referencing the temp directory can trip this.Wording 7: a concrete symlink claim that is false on disk
Produced by a read-only test invocation:
Unlike the others, this asserts a concrete filesystem fact, so we checked it: a recursive PowerShell scan of the entire traversed tree (hidden files included,
.venvincluded) for anyReparsePointattribute returned zero results. The directory sits inside a git worktree whose.gitis the standard plain pointer file, not a link. There is no symlink, Cygwin-emulated or otherwise, anywhere on the path. The stated reason is fabricated.Pattern across all seven
The boilerplate continuation ("final working directory of this cd-compound cannot be statically determined…") is appended to every variant regardless of fit — including commands with no writes, no relative paths, and (per above) no symlinks. Combined with the pipe-to-comma mangling shown in the previous comment, the picture is consistent: the analyser is producing confident, specific, and checkably wrong justifications, and each one costs the user a manual approval.
Also started getting these messages seemingly every few minutes
Confirming independently on a third machine — Windows 11, Git Bash, native install. 2.1.232 landed via self-update at 13 Aug 18:36 local (on-disk version files: 2.1.229, 2.1.231, then 2.1.232); prompts began the same evening on a repo that had run unattended for months without them. Same wordings as reported here, including reason 4 on a pure read.
Two things to add.
1. A second trigger in the same code path: command size
Separate from the cd-compound cases, a command that the rules would auto-approve is downgraded to
askwhen it is over 10,000 characters or names more than 8 path-like arguments:In the shipped 2.1.232 bundle these are
dge=1e4andHGa=8. Neither the message nor the constants appear in 2.1.229 or 2.1.231.Two real cases that tripped it here:
cat >> tests/foo.py <<'PY' … PYpython scripts/run_pg_tests.pywith 9 test files listedSame practical effect as the cd-compound false positives: unsuppressable, and
permissions.allowdoes not override it (consistent with #86630 — the downgrade happens after the allow decision).2. Impact: it blocks unattended runs, it is not only an annoyance
We run multi-hour autonomous sessions — an orchestrator plus subagents and workflows — on a solo-maintained repo with ~2,100 merged PRs. Interactively a prompt costs two seconds. Unattended it halts the run indefinitely, because there is nobody present to click. With
permissions.allowunable to override the gate, there is currently no configuration that keeps a Windows autonomous session running; the options are rewriting every command shape our agents emit, or pinning to 2.1.231 and losing the symlink fix.Worth noting how easily tool-generated commands hit this: subagents routinely prefix with
cd "<absolute path>" && …defensively, because a fresh agent cannot be certain of its working directory, especially with git worktrees in play. Nothing in our own prompts or skills teaches that shape — I checked — so it is a model default that recurs with every new agent rather than something a project can prompt its way out of.Mitigations that would help most, in order
cdto a quoted absolute literal as statically determinable. It is — the ambiguity only exists for computed or expanded targets.sed -n,grep,head,find) as write operations.permissions.allowmatch win, or provide a per-project opt-out, so unattended sessions have a supported configuration.Adding a fifth distinct wording, plus a negative control that narrows the scope to Git Bash specifically rather than Windows generally.
5.
Contains ansi_c_stringSame continuation as the four above:
Produced by this command:
Worth flagging on its own: this command contains no
cdtoken anywhere, yet the reason text asserts "this cd-compound". So the message is misleading about which construct actually tripped the analyser — here it appears to be the ANSI-C quoting ($'\r') and/or the variable-expanded redirect target (> "$f.lf"), neither of which involvescd. Anyone grepping their logs forcdwill under-count how often this fires.Negative control: Git Bash specific, not Windows generally
Same account, same 2.1.232, two Windows 11 machines:
| Machine |
bash.exeresolves to | Affected ||---|---|---|
| A |
C:\Program Files\Git\bin\bash.exe(Git for Windows) | yes, constantly || B |
C:\Windows\System32\bash.exe(WSL shim) | no, never |Machine B never routes shell through Git Bash, so the check cannot fire there at any version. Flagging it because a second machine looks like a clean control and silently is not one — we used machine B to rule out a version regression and lost several hours to that before checking which
bash.exeeach one resolved.Server-side feature flags were byte-identical across both machines (
tengu_permission_friction,tengu_harbor_permissions,tengu_classifier_disabled_surfaces,tengu_auto_mode_default_on), so this is not a per-machine flag rollout.Scale, for severity triage
On machine A, 2,118 of 4,519 Bash calls over five days (46.9%) match the
cd "<abs>" && ...shape alone; including variable-expanded redirect targets and ANSI-C quoting takes it to roughly 51%. In a worktree-per-session workflow every command is naturally path-scoped, so about every other tool call now prompts.Also confirming independently, consistent with #86630:
permissions.allowentries do not suppress these. The check runs before allow-rule matching, so no allowlist can address it — including an exact-prefix rule for the leading command.Independent confirmation on a fourth machine, plus one reason wording that does not appear anywhere in this thread yet.
Environment
MINGW64_NT-10.0-26200, bash 5.2.37, git 2.51.2.windows.1permissions.defaultMode: auto, 32permissions.allowrules, noask/deny, no enterprisemanaged-settings.json2.1.229→ 12 Aug 14:59,2.1.231→ 13 Aug 02:56,2.1.232→ 14 Aug 08:38 local, andclaude.exemtime is 08:38 the same morning. Prompting began that morning across all sessions, on repos that had run prompt-free for months.~/.claude/settings.jsonwas last modified three days earlier, so no config change is involved.Reproducing the string bisect from #86631 on this machine, as an independent check:
Wording 8:
Contains shell syntax (string) that cannot be statically analyzedVerbatim, with the same continuation as the others:
Grepping this thread's body and all comments,
shell syntax (string)does not yet appear, so this looks like an eighth distinct wording alongsidesimple_expansion,ansi_c_string, the untrusted-hooks/git one, the tilde-in-assignment one, and the analyzable-length one.To be precise about provenance: the wording above is quoted verbatim from the affected user, but they did not capture the paired command, so I cannot attribute it to a specific invocation with certainty. Commands of this shape from the same session are the plausible source — a
cd-compound whose later segments contain ordinary single-quoted literals:Nothing there writes through a symlink, and the quoted literals are
'\r'and'\n'.The trigger is the dominant agent shell idiom, and users cannot mitigate it
Worth stating explicitly, because it bears on severity. The Bash tool's own description tells the model:
In practice the model emits
cd <project> && ...anyway, and reliably so after anything perturbs the shell's working directory — onecdinto a git worktree that is not followed by acdback, and every subsequent command gets a defensivecd <project>;prefix for the rest of the session. That happened in one session here and produced roughly 40 consecutive manual approvals, every one of them a read-only or repo-local command in a repo the user owns.Two consequences:
allow→ask, so adding apermissions.allowrule for the offending shape does not suppress the prompt. That leaves affected Windows users with no configuration-level mitigation at all — only rolling back, or hoping the agent stops usingcd.The only mitigation that worked here was agent-side: dropping
cd-compounds entirely in favour ofgit -C <abs path>and the already-persistent working directory. That is a fine long-term habit, but it is not something a user can switch on, and the model has no way to know it is needed until the prompts start.I would add that the changelog entry quoted in #86631 describes a real bypass worth fixing, and nothing here argues against hardening it. The issue is the blast radius: gating on "there is a
cdand something non-literal somewhere in this command" catches essentially all agent-issued shell on Git Bash, including pure reads, while a symlink-free repo has nothing for the check to protect.Additional data points from a Windows 11 + Git Bash setup running several concurrent Claude Code sessions; the matrix below was measured with a positive control in a session running v2.1.232:
2x2 matrix (same session, same day):
| form | scratchpad dir | normal repo dir |
|---|---|---|
|
t="C:/abs/path" && echo x > "$t/f.txt"(variable-assigned absolute, no cd) | no prompt | no prompt ||
cd "$dir" && echo y > f.txt(cd-compound + relative target) | prompt | prompt |The positive control matters: the clean runs are a measurement, not an absence — the same probe discriminates. Notably, in a write target, plain
$varexpansion of an inline-assigned absolute is resolved statically by the checker (no prompt, top row). This refines the earlier suggestion that the ambiguity covers all "computed or expanded" targets: it is not variable expansion per se that trips the gate. Note the scope — an expanded target ofcditself was not clean in our testing, so the discriminator appears to be cd-compound with a relative/expanded working directory, not the expansion.Verbatim dialog observed (on a read-only measurement command; the tree contains no symlinks —
core.symlinks=falseandfind -type lreturns nothing):Variants observed the same day, on other command shapes: "Contains simple_expansion", "Contains brace with quote character (expansion obfuscation)", "This Bash command contains multiple operations".
Consistent with the separate
ansi_c_stringreport: there the trigger cannot be the variable-expanded redirect target alone, since that form is clean in our matrix — which points at the ANSI-C quoting or the command substitution instead.Impact: on the auto-mode rollout day this produced dozens of prompts/hour across three working sessions. Consistent with #86630, where
permissions.allowrules reportedly do not suppress these prompts.Workaround that removes most prompts by form (may help others while this is triaged): create probe/temp files via the editor's file-write tool instead of bash heredocs, use absolute literal or variable-assigned-absolute write targets, and avoid cd-compounds with relative writes. The residual cases are probes that genuinely need a relative cwd.
Follow-up from the same setup as my earlier comment (Windows 11, Git Bash, v2.1.232), with three additional measurements that may save others some diagnosis time. All measured today, with a human watching the screen in each run, and no PreToolUse hook matching Bash configured in the measuring session (checked — so nothing could have suppressed or injected a prompt decision).
1. A
cdthat resolves to the current working directory is a no-op and does not prompt — measured, both combinations.| probe (cwd = the target dir itself) | result |
|---|---|
|
cd "<current dir>" && git status| no prompt ||
cd "<current dir>" && echo x > file| no prompt (file written) || positive control:
cd "<other dir>" && <relative write>— same session | prompt |This matches the documented behavior (the permissions doc notes the no-op exemption for the
cd+gitcase) and extends it: it also held for thecd+redirect combination here. The practical consequence for agent workflows: the defensivecd <project> && ...prefix that models habitually emit is harmless when the session is already in that directory — the prompts come only fromcdinto a different directory. If your agent's project directory matches its cwd, that entire class of prompt disappears on its own.**2. The analyzer reads the content of string literals: a commit message that merely quoted commands triggered the gate.**
A
cat notes.md >> LOG.md && git add ... && git commit -m "<message>" && git push— a routine append plus commit, with no executablecdanywhere in the command — prompted with:In this case the only
cd-compounds present were inside the quoted commit message text, where the message happened to describe shell commands — yet the reason names "this cd-compound". To be precise about attribution: the executable part did contain the ordinary>> LOG.mdappend, so the "redirect shape" half of the wording may refer to that; but the cd-compound the reason names does not exist in executable position — it only occurs as quoted prose. At minimum the reason misattributes the construct (consistent with the misleading-reason reports in #76718); at worst the analyzer is parsing string-literal content as syntax.A later commit of the same shape prompted with "Contains shell syntax (command) that cannot be statically analyzed" (a sibling of the
shell syntax (string)wording reported above). That second case is reported for the wording only, and to be exact about it: that command did contain command substitution, so "shell syntax (command)" is an accurate reason there — it is listed because the wording itself appears to be new, not as a misattribution.Practical tip either way: commit messages that quote trigger-shaped commands currently coincide with prompts — check the executable part of your command before assuming the screen points at a real hazard. "Unrecognized redirect shape" does not appear anywhere in this thread yet, so this looks like a ninth distinct wording, and this one comes with its paired command.
3. Small documented escape hatch worth knowing: a command whose only redirect target is
/dev/nulldoes not prompt (per the permissions doc) — so read-only patterns likegrep -r pattern . 2>/dev/nullare safe even in compound form.looks like an update is released an hour ago to address all the issues listed here. I am testing and will update either way. Wish me Luck
https://github.com/anthropics/claude-code/releases/tag/v2.1.233#:~:text=Windows%3A%20fixed%20auto,a%20later%20release