macOS sandbox unusable: Seatbelt profile passed inline via 'sandbox-exec -p' exceeds ARG_MAX with many git worktrees

Open 💬 2 comments Opened Jul 2, 2026 by ehsan

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

  1. 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.
  2. With the sandbox enabled, run any Bash command, e.g. printf ok.
  3. 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_MAX1048576
  • Environment size → ~12 KB (not the cause)
  • Sandbox disabled → all commands work
  • Sandbox enabled → even printf ok fails
  • Reconstructing the profile from git state confirms the file-write deny rules are the dominant term and scale with worktree/repo count

Suggested fixes

  1. Primary: write the profile to a temp file and use sandbox-exec -f <file> instead of -p <inline>. File-based profiles have no ARG_MAX limit and this is a minimal change.
  2. Alternative: use the in-process sandbox_init(3) API (already referenced in the binary) rather than exec-ing sandbox-exec with the profile in argv.
  3. 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.
  4. 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 raw E2BIG).

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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗