anthropics / anthropics/claude-code

[BUG] 2.1.277: sandbox.excludedCommands no longer matches an executable whose basename is `read`

Open
#95,560 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:sandbox bug has repro platform:macos regression
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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.