[BUG] Permission prompt incorrectly triggers on cd instead of the actual command in compound bash statements
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?
When Claude Code generates a compound bash command using && (e.g.: cd /some/path && git add file && git commit -m "msg"), the permission approval prompt incorrectly identifies cd as the action requiring approval, showing a cd:* permission request instead of prompting for the meaningful actions (git add, git commit).
The problem repeats on every execution. The cd:* prompt keeps appearing and cannot be whitelisted for the session, making the workflow unusable.
What Should Happen?
In earlier versions, the same compound command correctly prompted for git add and git commit as the actions requiring permission, and allowed the user to whitelist them for the session.
Error Messages/Logs
Steps to Reproduce
- Open Claude Code in a project
- Ask Claude to stage and commit a specific file (e.g. CLAUDE.md)
- Claude generates a command like: cd /path/to/repo && git add CLAUDE.md && git commit -m "update"
- Observe the permission prompt . It asks for approval of cd:* instead of git add / git commit
- Approve it. The prompt appears again on the next execution, it cannot be whitelisted
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
Around 2.1.40 it was working fine
Claude Code Version
2.1.52
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Windows Terminal
Additional Information
_No response_
47 Comments
I'm seeing this too, in my case the the /some/path in "cd /some/path" is the directory I launched claude from so it shouldn't even be necessary.
EDIT: I'm also on Windows.
I have the similar issue:
<img width="1089" height="437" alt="Image" src="https://github.com/user-attachments/assets/8701e20d-36ef-4aeb-a4f2-4f35403fb5bc" />
In all commands, the Claude code tries to
cdto current directory.My guess is a path comparison is happening and failing due to different normalized forms being compared, comparing /c/code/project and c:\code\project, for instance. I noticed that when I tell Claude to stop using "cd " first with the project directory it will use the -C parameter with git (git -C <project_directory).
@fschwiet : Thank your, I will try this. I tried to adjust instructions to use
cdonly when needed, but it does not work.Or maybe this will finally convince me to buy Mac :)
@kosinal I'm finding the instructions added to claude.md aren't helping that much, every session we go through the same process where I have to tell it to stop doing a "cd <project dir> &&" then tell it stop doing the "git -C <projectDir>" to prevent excessive permission checks.
@fschwiet interesting, where do you put it instead? Directly into prompt, or into skill/agent?
It's in my CLAUDE.md and when that is ignored I deny permission requests that are changing paths and tell it not to do that. It still needs to be reminded every session, so I don't have a good fix.
I'm having some success with the following _very_ explicit workaround in
CLAUDE.md:Same issue here
I put CAPITALIZED instructions in CLUDE.md as well as stored in the memory files, but at best I get a 50% reduction.
Anyone wants to bisect a version that works?
I have been able to circumvent this by creating hooks that block command using this chaining and instructs it otherwise.
I tried this but couldnt get it to work, at least not on
git bashon Windows. It seems like Claude’s own security checks is triggered before any hooks run:"Compound commands with cd and git require approval to prevent bare repository attacks"
Prompting on
cdinstead ofgit addorgit commitbreaks the whole approval model. Once the control point attaches to shell glue instead of the mutating step, you lose both signal and usable whitelisting. I'm working on Daedalab around that exact boundary: approve the risky action, not the wrapper. If you're curios: www.daedalab.app@edgariscoding I just checked my files and I have edited the comment to reflect what I have in the settings.json. You need to use
$CLAUDE_PROJECT_DIRin the path and that works for me. If that doesn't work for you, open a session with Claude and get it to iterate until the path works, because this definitely works for me and has been really niceSweet Odin's Raven, please fix this
Ah thanks I got it to work! It works perfectly as a workaround thank you!
Anthropic, i beg of you, please fix this on windows, it's so annoying
Please please fix this. I've tried all the hacks/fixes and they are partial fixes at best. The hooks solution works but only for top level agents; once subagents get involved it breaks again.
Not sure how you got it to work, mind sharing? The JSON hook stuff is already invalid JSON to being with, missing a brace.
What worked for me:
I was not able to get this to work with WSL. I eventually got it to work after converting the bash script to a local non-WSL js script and have the hook be:
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "node C:/Users/<username...>/.claude/local_hook_tools/no_cd_chaining.js"
},
{
"type": "command",
"command": "node C:/Users/<username...>/.claude/local_hook_tools/no_git_dash_c.js"
}
]
}
]
}
(I am working on Windows projects that actually need windows, so WSL is less useful here....streamdeck plugin and electron app.)
Blocking with hooks isn't working for me. The agents are too creative. Block
cd ... && ...and it switches tocd ...; .... Block that, and it switches togit -C .... Block that, and it switches topushd ...; ...block that and it switches toGIT_DIR=...andGIT_WORK_TREE=...- It would be impressive if it wasn't so frustrating.I haven't had this issue, I thought that subagents inherit parent hooks?
Is the blocking hook providing feedback in what to do like my examples? The behavior you are describing sounds like you are blocking but not providing further guidance.
In my opinion this is essentially a breaking bug. Why isn't this a high priority fix!?
Just want to say that I have the hooks configured correctly and it worked for a bit but after a while Claude Code starts trying
pushdand other commands. This is despite the fact that I can see it output the hook commands.I thought so too. I setup the hooks, I put very strong instructions in claude.md, nothing works consistently enough to get through a whole superpowers plan. Especially because superpowers loves to make lots of very granular commits.
I should also note for the CC devs that this is happening on WSL, not just windows.
I haven't seen this issue since I switch to working in a Ubuntu VM sad trombone. That said I do get a bunch of permission requests for other reasons. That's interesting @CacTye is seeing this on WSL. One difference to be mindful of is that Windows filenames are case insensitive and according to google still case insensitive when accessing files from Windows (ie \\mnt\c\...)
The
cdpermission prompt on compound commands happens because the permission system parses the first command in the chain, not the actual action.Hook-based fix that works with subagents:
Register in
~/.claude/settings.json:This auto-approves any compound command starting with
cdwhile still blocking destructive patterns (rm -rf,git push --force,git reset --hard). Since hooks are defined in~/.claude/settings.json, they apply to all agents including subagents.Re: subagent issue — hooks in
~/.claude/settings.json(user-level) should inherit to subagents. If they're not firing, check that the hook is in the user settings file, not a project-level.claude/settings.json. You can verify withnpx cc-safe-setup --status.This worked for me
A PreToolUse hook can parse compound commands and auto-approve when all components are safe:
The blocking hook approach from @azlojutro is correct, but can be improved — the deny message can include the extracted real command so Claude retries immediately instead of doing pwd → cd → command as 3 separate calls.
.claude/hooks/no-cd-chaining.sh:The deny reason includes the stripped command, so Claude immediately retries with just the meaningful part instead of splitting into multiple calls.
For @edgariscoding's internal security check ("Compound commands with cd and git require approval to prevent bare repository attacks") — that fires before hooks. You can safely whitelist
cdin the permission system sincecdalone doesn't mutate anything:The actual dangerous commands (
git commit,rm, etc.) still require approval. Combined with the hook above, Claude learns to stop chainingcdafter a few blocked attempts, and thecd:*whitelist is a safety net for when it still does.Root cause: The permission matcher evaluates the entire compound command as a single string. When Claude runs
cd /path && git add file.txt && git commit -m "fix", the matcher seescdas the base command — notgit addorgit commit. So even withBash(git:*)in your allowlist, it won't match.Hook workaround (PreToolUse):
Add to
settings.json:This splits the compound command on
&&,||,;and checks each part. If all parts arecd,git, or safe builtins, it auto-approves. Otherwise it passes through to the default permission handler.Same root cause as #16561.
This is the same underlying issue as #16561 — Claude Code evaluates the entire compound string as one unit, so
cd /path && git add filetriggers acd:*permission prompt instead ofgit:*.For your specific case (
cd /path && git add file && git commit -m "msg"):cd /path→ safe (navigation)git add file→ safe (staging)git commit -m "msg"→ safe (committing)cd:*promptThe hook splits on
&&/||/;/|, checks each component individually, and only auto-approves when all are known-safe. Unknown commands fall through to the normal permission dialog.Note on the
cdto current directory issue mentioned in comments: Claude Code sometimes prependscd $(pwd)unnecessarily. The hook handles this too — it just treats the redundantcdas safe and evaluates the real command.Since GIT was the biggest offender on this, i downloaded the git mcp form modelcontextprotocol repo, told claude to enhance it a bit, and plugged it into claude itself and now i don't have this problem, it's been couple of days, but it works so far.
Just chiming in that this happens to me on macOS as well.
this has been going on for 6 weeks now. has anyone from anthropic acknowledged it?
I’m guessing they haven’t and probably won’t because they are testing Auto Mode On Apr 8, 2026, at 5:32 PM, Boognish @.***> wrote:boognish24 left a comment (anthropics/claude-code#28240)
this has been going on for 6 weeks now. has anyone from anthropic acknowledged it?
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: @.***>
How long would it take Mythos to fix this?
Instead of whack-a-moling with allow lists and duplicating the entire permission settings in a bash variable, just deny it:
same thing!
This is also still an issue for me. Trying to steer it through CLAUDE.md does not work, it will use other similar commands (git -C, Set-Location) on windows 11 with version 2.1.146.
since you close all other vaguely related issues:
claude constantly chains commands e.g.
cd ... && find ...orcd ... ; find ...and even though you specifically whitelist-approved all of these commands in isolation it will keep repeatedly asking for permission to run those. this alone heavily incentizes you to skip all permissions entirely, cuz there are no good workarounds. please fix it, if all commands of a chained command are whitelisted there is NO NEED to ask permissionI am just going to note that recently I've seen Claude ask for separate permissions for commands that have been &&'d together. But it doesn't always ask, leading me to think they are have a heuristic that detects some joined commands now.
The hooks I currently use is are https://github.com/fschwiet/nosabokit, it is a plugin that also handles chained git commands.
Still happening for me, despite the explicit instructions:
Note, in Claude Desktop there is a reasoning given for these commands requiring permission:
It should definitely exclude cases where the repo is in a subdirectory of the working directory, as that obviously is trusted.
What works for me is this snippet in the .claude/settings.local.json:
Workaround
Add
"Bash(cd *)"to thepermissions.allowarray in~/.claude/settings.json:This suppresses the false
cdprompt entirely. Any meaningful commands after&&that aren't already in the allow list will still get their own permission prompts.Note on PreToolUse hooks as a bypass mechanism
If you're using a PreToolUse hook (e.g. clawband), hooks that return an explicit
permissionDecision: "allow"in their JSON output will bypass Claude Code's native permission check entirely:This means a well-written PreToolUse hook could intercept compound commands, correctly identify which segment needs permission, and return an explicit allow for the
cdportion — bypassing this faulty native check while still evaluating the rest of the pipeline. Worth considering as an alternative fix path.Adding some tested findings, since this thread keeps growing and the popular workarounds don't actually hold up.
TL;DR: the
Bash(cd *)/Bash(cd:*)allow-list workaround does not suppress this prompt. I tested it on macOS (so it's not just a Windows path-normalization thing):Bash(cd:*)to globalsettings.json→ still prompted.Bash(cd *)space-spelling from the upvoted workaround, restarted again → identical prompt.The reason is that this isn't a normal allow-list miss. The message —
— is a separate, hardcoded security gate that doesn't consult the permission allow list at all. It's guarding "is this directory trusted," not "is this command allowed," so no
Bash(...)rule of any spelling can silence it. TheBash(cd *)suggestion fixes a different symptom in this thread (the one wherecdgets mis-identified as the command prefix). Worth pinning that distinction, because people keep recommending it for the bare-repo case and it quietly does nothing.Bonus: it fires even when the
cdtarget is a subdirectory of the current working directory — i.e. a path that is, by definition, already trusted. So the gate is protecting me from a repo I'm already sitting inside.Following up on my earlier comment with a workaround that actually holds up — a
PreToolUsehook that returnspermissionDecision: "allow", which is the only thing I've found that overrides the bare-repo gate instead of racing against it.The important property: it auto-allows a
cd … && …chain only when every segment is read-only. Socd /path && git statusruns silently, butcd /path && git commit(orrm,git push,git fetch, a build, anything unrecognized) falls right back to the normal permission prompt. You're suppressing the false prompt without handing the model a blanket pass.$HOME/.claude/hooks/allow-cd-readonly-chains.sh:Wire it into
~/.claude/settings.json:Setup:
chmod +x ~/.claude/hooks/allow-cd-readonly-chains.shhooksblock above (requiresjqandawkon PATH — both standard on macOS/Linux; on Windows point it at your Git Bashbash).Verified behavior (macOS):
| Command | Result |
| --------------------------------------------------- | ---------------- |
|
cd /path/to/repo && git status --short && echo ok| runs silently ✅ ||
cd /path/to/repo && git diff \| head| runs silently ✅ ||
cd /path/to/repo && git commit -m x| still prompts 🔒 ||
cd /path/to/repo && git fetch| still prompts 🔒 ||
cd /path/to/repo && rm -rf …| still prompts 🔒 |One gotcha I'll save the next person: do not split the command with
sed— BSD/macOSseddoesn't expand\nin the replacement, so the split no-ops and the whole chain matches the leadingcdprefix, which means it would happilyallowacd /x && rm -rf ~.awk'sgsub(…, "\n")does the right thing cross-platform. Adjust the read-only allowlist inis_safe()to taste.And yes — this is still a workaround for a gate that over-fires on directories at or below the cwd. Would love for that to just... not need a hook someday. 🙂