anthropics / anthropics/claude-code
[BUG] 2.1.277: sandbox.excludedCommands no longer matches an executable whose basename is `read`
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 146k
- Forks
- 23.8k
- 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 (2.1.278)
What's Wrong?
Since 2.1.277, a sandbox.excludedCommands entry no longer matches an executable whose basename is read, even when the command is a single absolute-path invocation. The command runs inside the sandbox as if the entry were absent. A sibling executable named write, with an identical entry, is still excluded. Same settings file and prompt under each binary; only the Claude Code version changes.
We hit this because our project's CLI tools are executables named read and write (one pair per tool directory), excluded from the sandbox and carrying their own narrower permissions. After the update every read tool failed and every write tool kept working.
What Should Happen?
An excludedCommands entry for an absolute-path executable matches regardless of its basename, as it did in 2.1.276. A path containing a slash never resolves to the read shell builtin, so a guard for the builtin only needs to cover the bare word.
Error Messages/Logs
/bin/sh: /private/tmp/cc-read-repro/bin/read: Operation not permitted
(exit code 126)
Steps to Reproduce
Self-contained, no project needed. It uses the side-by-side binaries the native installer keeps under ~/.local/share/claude/versions/.
D=/private/tmp/cc-read-repro; rm -rf $D; mkdir -p $D/bin $D/work
for n in read write; do printf '#!/bin/sh\necho "ok from %s: $*"\n' $n > $D/bin/$n; chmod +x $D/bin/$n; done
cat > $D/settings.json <<EOF
{
"permissions": { "allow": ["Bash($D/bin/read *)", "Bash($D/bin/write *)"] },
"sandbox": {
"enabled": true,
"autoAllowBashIfSandboxed": true,
"allowUnsandboxedCommands": false,
"failIfUnavailable": true,
"excludedCommands": ["$D/bin/read *", "$D/bin/write *"],
"filesystem": { "denyRead": ["$D/bin"] }
}
}
EOF
P="Run exactly these two shell commands, each in its own Bash tool call, then print each command's exit code and output verbatim. 1: $D/bin/read hello 2: $D/bin/write hello"
for v in 2.1.276 2.1.278; do echo "== $v"; (cd $D/work && ~/.local/share/claude/versions/$v -p --setting-sources '' --settings $D/settings.json --model haiku "$P" </dev/null); done
Result:
| Version | bin/read hello |
bin/write hello |
|---|---|---|
| 2.1.276 | exit 0, ok from read: hello |
exit 0, ok from write: hello |
| 2.1.277 | exit 126, Operation not permitted |
exit 0, ok from write: hello |
| 2.1.278 | exit 126, Operation not permitted |
exit 0, ok from write: hello |
The denyRead entry is what makes the difference visible: when the exclusion is skipped, the sandboxed shell cannot read the script.
Claude Model
Not sure / Multiple models (the repro uses --model haiku; the model only relays the Bash result)
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.276
Claude Code Version
2.1.278 (also 2.1.277)
Platform
Anthropic API
Operating System
macOS (Darwin 25.5.0, arm64, native installer)
Terminal/Shell
Non-interactive/CI environment (claude -p)
Additional Information
In case it saves someone time: the 2.1.277 and 2.1.278 bundles contain a one-element set, new Set(["read"]), that the exclusion coverage check consults, and 2.1.275 and 2.1.276 don't have it. It reads like a guard for the read builtin that compares the basename. If so, limiting it to commands without a slash would keep the guard and fix this case.
This is in the same area as #95455 (the 2.1.277 "every part must match" change also dropping single commands with pre-subcommand flags), though the trigger here is different.
Our workaround is holding 2.1.276 with DISABLE_AUTOUPDATER=1, so there's no urgency on our side beyond wanting to get back on current releases. Happy to test a fix or provide anything else that helps. Thanks for the work on the sandbox.
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 by running the self-contained reproduction against versions 2.1.276 and 2.1.278, then inspect the sandbox excluded-command coverage check described in the report, including the new Set(["read"]) guard. Done means an absolute-path executable named read is excluded like write, while the bare read builtin guard remains effective and the reproduction succeeds on the current version.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- shell, typescript
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100