Skip to content

[BUG] sandbox.excludedCommands can't unsandbox an excluded tool in chained or cd calls, so git breaks under the sandbox #103677

Open

Description

@mskonovalov

Preflight Checklist

  • I have searched existing issues and this hasn't been reported yet
  • This is a single bug report

What's Wrong?

With "sandbox": { "enabled": true, "excludedCommands": ["git *", "gh *"] } in ~/.claude/settings.json, git still runs sandboxed in most of the shapes the model actually emits, so git breaks (GPG commit signing, fetch/push over HTTPS). There is no setting that makes an excluded tool run unsandboxed in every call shape.

Measured on 2.1.286, macOS (Seatbelt), desktop app Code tab, auto mode:

Call (one Bash tool call) Result
git ls-remote origin HEAD unsandboxed, works
git -C . ls-remote origin HEAD sandboxed: deny network-outbound github.com:443
git --git-dir=<repo>/.git ls-remote origin HEAD sandboxed
GIT_DIR=<repo>/.git git ls-remote origin HEAD sandboxed
git -C <worktree> commit -F msg.txt sandboxed: gpg: can't connect to the keyboxd: Operation not permitted, commit fails
cd <repo> && git fetch origin main sandboxed (documented)

The -C / --git-dir part is #95455. This issue is about the rest: under the 2.1.277 "every part must match" rule, any cd, && chain, pipe, loop, $(...) or redirect makes the whole call sandboxed, even though the excluded tool itself must run unsandboxed to work.

Impact:

What Should Happen?

An excluded command should run unsandboxed whatever call shape it appears in. Two possible designs, either would solve it:

  1. Per-segment sandboxing: parse the call into its segments (Claude Code already does this for permission rules and for the "every part must match" check) and run only the segments matching excludedCommands unsandboxed, while the remaining segments stay sandboxed. cd <dir> segments would set the working directory for the next segment rather than force sandboxing.
  2. Opt-in setting, e.g. "sandbox": { "excludedCommandsMatch": "any" }: if any segment of a call matches excludedCommands, run the whole call unsandboxed (the pre-2.1.277 behaviour). It is off by default, so excludedCommands with a glob unsandboxes the entire Bash invocation, including commands that run before the excluded one #81157 stays fixed for users who don't opt in.

Separately, as far as I can tell from the docs, a PreToolUse hook cannot choose sandboxed vs unsandboxed per call, so users can't build either behaviour themselves. Allowing a hook to set that per call would also work.

Steps to Reproduce

  1. ~/.claude/settings.json: {"sandbox": {"enabled": true, "excludedCommands": ["git *"]}}
  2. In a git repo, ask Claude to run, as separate Bash calls:
    • git ls-remote origin HEAD → works
    • git -C . ls-remote origin HEAD → deny network-outbound github.com:443
    • cd . && git ls-remote origin HEAD → sandboxed (documented behaviour)
  3. With GPG commit signing configured (commit.gpgsign=true, keyboxd), run git -C <repo> commit -m test → gpg: can't connect to the keyboxd: Operation not permitted.

Claude Code Version

2.1.286 (Claude Code)

Operating System

macOS

Terminal/Shell

Claude desktop app (Code tab), zsh

Additional Information

Related: #95455 (pre-subcommand flags), #95532 (redirection defeats exclusion), #81157 (the bypass the 2.1.277 change fixed).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:sandboxbugSomething isn't workinghas reproHas detailed reproduction stepsplatform:macosIssue specifically occurs on macOS

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions