[BUG] Sandbox deny rules with mid-path globs cause E2BIG on Linux — per-file bwrap expansion instead of per-directory overlay
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
With sandbox.enabled: true in my org's managed-settings.json, consider this simple Claude Code session:
! cat .claude/settings.json
⎿ {
"permissions": {
"deny": [
"Read(foo/*/data/cache/**)"
]
}}
❯ use Bash to echo "hello world"
● Bash(echo "hello world")
⎿ Interrupted · What should Claude do instead?
● The command was interrupted. What would you like me to do instead?
Bash simply croacks. If I check Claude's debug log:
2026-04-10T23:35:41.677Z [DEBUG] Shell exec error: E2BIG: argument list too long, posix_spawn '/usr/bin/zsh'
2026-04-10T23:35:41.679Z [DEBUG] Bash tool error (131ms): Shell command failed
But when I delete that single deny rule, Bash works fine!
Summary (everything below is from Claude, but has been verified and is accurate!)
On Linux, when permissions.deny contains Read(...) rules with mid-path glob wildcards (e.g., Read(foo/*/data/cache/**)), the sandbox appears to expand the glob against the actual filesystem and generate a separate bubblewrap bind-mount argument for every matching file. In a project with thousands of generated/cached files under the matched path, the resulting bwrap argument list exceeds Linux's ARG_MAX, and posix_spawn fails with E2BIG.
This does not occur on macOS.
Environment
- Claude Code v2.1.101
- Linux (RHEL-family), not containerized
ARG_MAX: 2,097,152 (2MB)MAX_ARG_STRLEN: 131,072 (128KB) —PAGE_SIZE * 32ulimit -s: 8192- Shell snapshot size: ~116KB (from
--debuglog) - Shell environment size: ~3.7KB
- Sandbox enabled via managed-settings.json (
sandbox.enabled: true,sandbox.failIfUnavailable: true) - Bubblewrap + socat
Steps to reproduce
- On Linux with sandbox enabled, have a project containing a directory tree with thousands of files (e.g., App cache:
foo/*/data/cache/— mine has ~2,700 entries across two subdirectories) - Add a single deny rule to
.claude/settings.json:
``json``
{ "permissions": { "deny": ["Read(foo/*/data/cache/**)"] } }
- Start Claude from the project directory
- Run any Bash command (e.g.,
echo hello)
Result: Every Bash command fails silently. The model sees "Interrupted" and hallucinates explanations. Only claude --debug reveals the real error:
Shell snapshot created successfully (118918 bytes)
Spawning shell without login (-l flag skipped)
Shell exec error: E2BIG: argument list too long, posix_spawn '/usr/bin/zsh'
Bash tool error (509ms): Shell command failed
Diagnosis
I isolated the cause to a single permissions.deny rule:
"Read(foo/*/data/cache/**)"
Key observations:
- Only
Read(...)deny rules trigger it. The following rules with identical or broader wildcards do NOT cause E2BIG, even when present simultaneously: Edit(**/data/cache/**)— no problemWrite(**/data/cache/**)— no problemGrep(foo/*/data/cache/**)— no problem
This suggests only Read (and possibly Edit) deny rules are translated into OS-level sandbox filesystem restrictions. Write, Grep, and Bash deny rules appear to operate only at the tool permission level.
- Rooted patterns are fine.
Read(build/**)works (only 8 files inbuild/). The issue is specifically mid-path wildcards that fan out across directories with many files.
- Patterns starting with
**are fine.Edit(**/data/cache/**)doesn't trigger it, suggesting the expansion logic skips patterns with no concrete prefix before the first glob character.
- Explicit paths fix it. Replacing the wildcard with the actual directory names works. See "Workaround" below.
- The problem scales with file count.
find foo/*/data/cache/ | wc -lreturns 2,704. At ~100+ bytes per bwrap argument, that's ~270KB of additional argv — enough to push past OS limits when combined with the ~116KB shell snapshot.
- Only occurs on Linux. The identical settings work fine on macOS, which uses
sandbox-execprofiles instead of bubblewrap bind-mounts.
- Only occurs in project directories. Running from
~(no project-level deny rules) works fine. The managed-settings deny rules alone don't trigger it.
getLinuxGlobPatternWarnings()— the/sandboxUI displays warnings about glob patterns not working on Linux, so this limitation is partially known. But the glob expansion still proceeds and causes the crash.
Workaround
Replace mid-path wildcards with explicit directory names:
- "Read(foo/*/data/cache/**)",
+ "Read(foo/bar/data/cache/**)",
+ "Read(foo/baz/data/cache/**)"
Suggested fix
When building bubblewrap arguments from denyRead glob patterns, resolve to the shallowest matching directories and emit one --tmpfs overlay per directory, rather than expanding ** into per-file bind-mounts. For foo/*/data/cache/**, the correct bwrap output should be two directory overlays — not 2,704 individual file entries.
What Should Happen?
Claude's ability to use Bash should not be impaired by this single trivial deny rule.
Error Messages/Logs
Documented above
Steps to Reproduce
Documented above
Claude Model
Opus
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.101 (Claude Code)
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
iTerm2
Additional Information
_No response_
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗