anthropics / anthropics/claude-code
[BUG] 2.1.277: excludedCommands "every part must match" also drops single commands carrying a pre-subcommand flag (git -C, -c, --git-dir)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 147k
- Forks
- 24k
- PR merge metrics
- PR metrics pending
Description
Preflight Checklist
- I have searched existing issues and this hasn't been reported yet
- This is a single bug report
- I am using the latest version of Claude Code
What's Wrong?
2.1.277 includes this fix:
Fixed a
sandbox.excludedCommandsglob exempting an entire compound Bash command from the sandbox when only one part matched; every part must now match
That fix is correct and closes a real bypass (#81157). But it appears to over-match: a single command — no pipe, no chain, no redirect, therefore exactly one "part" — is also no longer exempted when it carries a flag before its subcommand.
With sandbox.excludedCommands: ["git *"]:
| Command | 2.1.276 | 2.1.277 |
|---|---|---|
git status --porcelain |
excluded | excluded |
git --no-pager status --porcelain |
excluded | excluded |
git --literal-pathspecs status --porcelain |
excluded | excluded |
git -C . status --porcelain |
excluded | sandboxed |
git -c core.pager=cat status --porcelain |
excluded | sandboxed |
git --git-dir=<p>/.git --work-tree=<p> status --porcelain |
excluded | sandboxed |
git --exec-path=/usr/lib/git-core status --porcelain |
excluded | sandboxed |
Every one of these is a single command and matches the glob git * as a plain string. Bare boolean pre-flags are unaffected; flags that take a value are not.
Impact, in order of severity:
-
git statusreturns plausible wrong output instead of failing. It exits 0 and produces well-formed porcelain that happens to be wrong: paths the sandbox masks (home-directory dotfiles such as.bashrcand.gitconfig, plus configured deny-read paths) are reported as untracked repository files. This reads as ordinary working-tree noise. In my case it was dismissed as exactly that, and the underlying condition went undiagnosed across several later commands, including a staging decision. Anything that parsesgit statusis affected. An error would have been strictly safer than a wrong answer. -
Credentialed operations fail with a misleading error.
git -C <path> fetch/pushfail withfatal: could not read Username for '<host>', because the credential helper cannot read its configuration inside the sandbox. The surfaced error points at credentials or networking rather than at sandboxing, which invites misattribution. -
A common pattern breaks.
git -C <dir> …is a standard way to act on another worktree without changing directory, including inside scripts and tooling.
Why this looks like over-matching rather than intent. If the goal were to exclude git's repo/config-retargeting flags on security grounds — they are genuine arbitrary-code-execution vectors (-c core.sshCommand=…, -c credential.helper=…, --exec-path= into a writable directory) — that would be defensible. But it is not mentioned in the changelog, which describes only the compound-command tightening; it is not in the settings documentation; --exec-path and -C are treated the same as -c while harmless value-less flags pass, a split that tracks "flag takes a value" rather than "flag is dangerous"; and the status failure mode is silent wrong output, which no intentional security control would choose.
What Should Happen?
A single command carrying pre-subcommand flags should be treated as one part, so git -C <path> status continues to match git * and run excluded — exactly as it did in 2.1.276.
If any of these flags are excluded deliberately, it should be documented in the settings reference and should fail loudly rather than returning sandbox-contaminated git status output.
Error Messages/Logs
# Same repo, same cwd, two separate Bash tool calls, 2.1.277.
$ git status --porcelain
# (clean — correct)
$ git -C . status --porcelain
?? .bashrc
?? .gitconfig
?? .profile
?? .zshrc
# …and further entries, one per sandbox-masked path.
# None of these files exist in the repository.
# Credentialed form:
$ git -C /path/to/repo fetch origin main
warning: unable to access '/path/to/repo/.gitmodules': Permission denied
fatal: could not read Username for 'https://<host>': No such device or address
# Same command without -C, immediately afterwards, succeeds:
$ git fetch origin main
From https://<host>/<owner>/<repo>
* branch main -> FETCH_HEAD
Steps to Reproduce
-
Enable the sandbox and add a
gitexclusion in~/.claude/settings.json:{ "sandbox": { "enabled": true, "excludedCommands": ["git *"] } } -
In any git repository, run this as a Bash tool call:
git status --porcelainNote the output — it reflects the true working-tree state.
-
As a separate Bash tool call (chaining would itself defeat the exclusion, correctly, under the 2.1.277 rule), run:
git -C . status --porcelain -
Compare.
-C .is a no-op, so the two should be identical. On 2.1.277 the second additionally lists sandbox-masked paths as untracked files.
The same split appears with git -c core.pager=cat status, git --git-dir=… --work-tree=… status, and git --exec-path=… status, while git --no-pager status and git --literal-pathspecs status remain correctly excluded.
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.276
Claude Code Version
2.1.277 (Claude Code)
Platform
Google Vertex AI
Operating System
Ubuntu/Debian Linux
Terminal/Shell
WSL (Windows Subsystem for Linux)
Additional Information
Not fully deterministic. Across sessions on the same machine and version, the same command shape failed, failed, then succeeded within about 60 seconds. Behaviour was stable and reproducible within a given session but not identical across sessions, which suggests the classification may be cached or computed per session. Flagging it because it makes the bug hard for users to characterise — my own first attempt produced a clean, reproducible-looking rule that did not hold outside that session.
Only tested on Vertex AI. Nothing about this looks provider-specific — it is in the Bash/sandbox layer — but I have not confirmed it on the first-party API or Bedrock.
Workaround. Change directory in its own command, then run plain git:
cd <dir>
git status --porcelain
Note that (cd <dir> && git …) is not a workaround — that is genuinely a compound command, so the 2.1.277 rule correctly declines to exempt it.
Related issues.
- #81157 — the compound-command bypass that this 2.1.277 change fixes. This report is about that fix over-matching, not a request to revert it.
- #82109 —
excludedCommandsskipped when the excluded command is inside a shell loop; same family, command structure defeating the match. - #92445 —
excludedCommandsintermittently proxied; possibly related to the non-determinism above.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in the Bash/sandbox layer and reproduce the settings.json excludedCommands case by comparing plain git status with git -C . status. Trace how pre-subcommand flags are classified versus compound commands. Done means a single flagged git command matches the exclusion while compound commands still require every part to match, with coverage for the reported flag forms.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- bash, git, python
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100