macOS sandbox unusable: Seatbelt profile passed inline via 'sandbox-exec -p' exceeds ARG_MAX with many git worktrees
Environment
- Claude Code:
2.1.197 - OS: macOS (Darwin, arm64)
- Sandbox: enabled (default)
Summary
On macOS, every sandboxed Bash command fails with:
E2BIG: argument list too long, posix_spawn '/bin/zsh'
…including trivial commands like printf ok. The sandbox becomes 100% unusable and the only workaround is disabling it entirely. The environment is tiny (~80 vars, ~12 KB), so the environment is not the cause — the sandbox wrapper itself is.
Root cause
Claude Code builds the macOS Seatbelt profile and passes it inline as a command-line argument. The assembled invocation is (paraphrased from the bundled @anthropic-ai/sandbox-runtime):
env <unset/set env vars> /usr/bin/sandbox-exec -p "<ENTIRE SEATBELT PROFILE>" /bin/zsh -c "<user command>"
…which is then shell-quoted and executed as /bin/zsh -c "<that whole string>". So the full profile lives inside argv, and argv + envp must fit within ARG_MAX (1,048,576 bytes on macOS).
The profile's file-write section grows linearly with the number of git worktrees/repos under the configured working directories. For each worktree and repo it emits one (deny file-write* …) rule per git config file, e.g.:
(deny file-write* (literal "<repo>/.git/worktrees/<name>/config.worktree") (with message "…"))
(deny file-write* (literal "<repo>/.git/worktrees/<name>/config.worktree.lock") (with message "…"))
(deny file-write* (literal "<repo>/.git/worktrees/<name>/commondir") (with message "…"))
…
On a developer machine with a large number of git worktrees across the working roots (order of ~1,000 worktrees / ~100 repos in my case), this profile crosses ~1 MB. Once it does, the outer posix_spawn('/bin/zsh', …) fails with E2BIG before any command runs — so nothing executes and the sandbox is completely broken.
Steps to reproduce
- On macOS, configure a workspace whose working directories contain (directly or via an additional working directory) a large number of git repos/worktrees — enough that the generated Seatbelt profile exceeds ~1 MB.
- With the sandbox enabled, run any Bash command, e.g.
printf ok. - Observe
E2BIG: argument list too long, posix_spawn '/bin/zsh'.
Expected: the command runs under the sandbox regardless of how many worktrees/repos exist.
Actual: every sandboxed command fails with E2BIG; the sandbox is unusable until disabled.
Diagnostics gathered
getconf ARG_MAX→1048576- Environment size → ~12 KB (not the cause)
- Sandbox disabled → all commands work
- Sandbox enabled → even
printf okfails - Reconstructing the profile from git state confirms the file-write deny rules are the dominant term and scale with worktree/repo count
Suggested fixes
- Primary: write the profile to a temp file and use
sandbox-exec -f <file>instead of-p <inline>. File-based profiles have noARG_MAXlimit and this is a minimal change. - Alternative: use the in-process
sandbox_init(3)API (already referenced in the binary) rather than exec-ingsandbox-execwith the profile inargv. - Secondary (defense-in-depth): consolidate the git-protection rules so profile size doesn't grow linearly with worktree count — e.g. one
(deny file-write* (subpath "<repo>/.git"))-style rule per repo instead of one literal per config file per worktree, and/or cap the number enumerated. - Guard: if the assembled command would exceed
ARG_MAX, fall back to a file-based profile (or at minimum emit a clear diagnostic instead of a rawE2BIG).
Impact
Sandbox mode is unusable on any machine with a large git-worktree footprint in scope, forcing users to disable the sandbox to get any work done.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗