Preflight Checklist
What's Wrong?
On WSL2, every Bash tool call fails immediately with Exit code 126 and "Request interrupted by user for tool use" (no actual user interruption occurs) when sandbox.enabled: true is set with a non-trivial filesystem.denyRead list and/or permissions.deny Read(...) patterns.
Root cause (confirmed via strace): Claude Code constructs a bubblewrap (bwrap) sandbox wrapper command and passes it as a single -c argument to /bin/bash. This single argument exceeds Linux's MAX_ARG_STRLEN = PAGE_SIZE × 32 = 128 KB limit (defined in include/uapi/linux/binfmts.h), causing posix_spawn to return E2BIG.
Important: This is NOT the ARG_MAX (2 MB) limit. The kernel enforces a separate per-argument size cap via MAX_ARG_STRLEN.
Baseline checks (all normal — ruling out environmental causes):
getconf ARG_MAX = 2097152
ulimit -s = 8192
- Process
environ sizes are in the single-digit KB range
- Bare
bash -c 'echo hello' works
node -e "child_process.spawn('bash', ['-c', 'echo hello'])" works
- Same Claude binary, smaller workspace / smaller deny list → works
Independent verification that narrowed this to the harness/bwrap layer:
- Both the VS Code extension and the standalone CLI (
claude --dangerously-skip-permissions, same underlying claude binary) reproduce the failure identically.
- VS Code window reload, full VS Code restart (process kill + relaunch), and switching permission mode across
plan / default / acceptEdits / bypass all fail to resolve it.
- User-side PreToolUse Bash hooks were independently verified as NOT the cause: invoking them directly with the documented stdin JSON payload returns exit 0.
- No permission prompt appears in the VS Code UI during failure — the tool call is terminated before any permission layer is reached, consistent with a low-level
posix_spawn failure.
What Should Happen?
Bash tool calls should execute regardless of how many sandbox deny/allow rules the user has configured. The bwrap invocation should not hit kernel argument length limits.
Suggested fixes (in order of preference):
- Invoke
bwrap directly with an argv array instead of wrapping it in /bin/bash -c "<string>". The current design forces the entire bwrap configuration into a single argument.
- Pass bwrap configuration via
--args-fd / --args <file> / stdin so the sandbox policy does not compete with argv space.
- Deduplicate merged deny paths. Observed duplication:
Read(//**/foo) and Read(**/foo) pattern pairs in permissions.deny appear to each expand into their own --tmpfs entries.
- Reconsider the unconditional injection of proxy environment variables (
HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, FTP_PROXY, GIT_SSH_COMMAND, DOCKER_*, CLOUDSDK_PROXY_*, GRPC_PROXY, etc.) — these contribute ~25 --setenv entries per Bash call regardless of user setting.
Error Messages/Logs
Shape of the failing execve (sanitized; proxy env vars and internal mounts summarized):
execve("/bin/bash", ["/bin/bash", "-c",
"bwrap --new-session --die-with-parent --unshare-net
--bind <claude_http_sock> <claude_http_sock>
--bind <claude_socks_sock> <claude_socks_sock>
--setenv SANDBOX_RUNTIME 1
--setenv TMPDIR <tmpdir>
[~25 proxy-related --setenv entries omitted: HTTP_PROXY, HTTPS_PROXY,
ALL_PROXY, FTP_PROXY, RSYNC_PROXY, DOCKER_HTTP_PROXY, DOCKER_HTTPS_PROXY,
CLOUDSDK_PROXY_*, GRPC_PROXY, GIT_SSH_COMMAND,
CLAUDE_CODE_HOST_HTTP_PROXY_PORT, CLAUDE_CODE_HOST_SOCKS_PROXY_PORT, ...]
--ro-bind / /
[several --bind entries for /tmp/claude-*, .npm/_logs, .claude/debug, workspace]
[many --tmpfs / --ro-bind entries, one per denied path in
sandbox.filesystem.denyRead + permissions.deny Read(...), which is the
dominant contributor to the combined argument length]
<user workload>"
], 0x... /* ~58 vars */
) = -1 E2BIG (Argument list too long)
Captured with:
strace -f -e trace=execve -s 2000 -o /tmp/claude-trace.txt -- \
claude --dangerously-skip-permissions
# → accept warning → send "echo hello" → observe failure → Ctrl+D
grep "execve.*bash" /tmp/claude-trace.txt
grep "E2BIG" /tmp/claude-trace.txt
Steps to Reproduce
-
Use a recent WSL2 kernel on Windows.
-
In ~/.claude/settings.json, enable sandbox with a realistic deny configuration. Minimal reproducer uses roughly this shape — the total rule count across both sections matters more than specific paths:
{
"permissions": {
"deny": [
"Read(//**/.env)", "Read(**/.env)",
"Read(//**/.env.local)", "Read(**/.env.local)",
"Read(//**/*.pem)", "Read(**/*.pem)",
"Read(//**/*.key)", "Read(**/*.key)",
"Read(//**/secrets/**)", "Read(**/secrets/**)"
// ... total ~30 patterns typical of a conservative production config
]
},
"sandbox": {
"enabled": true,
"filesystem": {
"denyRead": [
"**/.env", "**/.env.*",
"**/secrets/**", "**/*.pem", "**/*.key"
// ... plus typical host-level path patterns
],
"allowRead": [
"**/.env.example", "**/.git/config"
]
}
}
}
-
Start a fresh Claude Code session (VS Code extension or CLI: claude --dangerously-skip-permissions).
-
Send any message that invokes the Bash tool, e.g. run echo hello.
-
Observe: the tool call fails immediately with Exit code 126 and [Request interrupted by user for tool use].
Minimal workaround confirmed: collapsing sandbox.filesystem.denyRead to a single broad entry (e.g. ["~/"]) with narrow allowRead carve-outs for the binaries/caches/paths actually needed reduces bwrap argv below the 128 KB threshold and restores Bash tool functionality.
Claude Model
Opus
Is this a regression?
I don't know
Claude Code Version
2.1.114 (Claude Code)
Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
WSL (Windows Subsystem for Linux)
Additional Information
Environment (minimal)
- Recent Microsoft WSL2 kernel (6.x series) on Ubuntu-based distro
- bubblewrap via standard distro package
getconf PAGE_SIZE = 4096, so MAX_ARG_STRLEN ≈ 131072 bytes (128 KB)
Hypothesis on argument-length growth
The failing command string consists mostly of three categories:
- Proxy
--setenv (~25 entries) — auto-injected by Claude Code, not user-configurable. Roughly 4-6 KB.
- Required
--bind / --ro-bind (6-8 entries) — workspace + Claude's own runtime sockets / debug paths. Not user-adjustable.
--tmpfs per denied path — this is the dominant growth factor. Both permissions.deny Read(...) patterns and sandbox.filesystem.denyRead entries expand here, apparently without deduplication.
Users who enable a conservative sandbox with explicit denylists routinely have enough deny patterns (30+ patterns across permissions.deny and sandbox.filesystem.denyRead) to exceed the 128 KB threshold.
Related issues
Other tools for comparison
Severity
Medium-High. Users with realistic permissions.deny / sandbox.filesystem.denyRead configurations hit this on every Bash tool call, which blocks any shell-dependent workflow. The user-side workaround (replacing granular deny patterns with a single broad deny plus narrow allowRead carve-outs) resolves the symptom but reduces isolation granularity.
Preflight Checklist
What's Wrong?
On WSL2, every Bash tool call fails immediately with
Exit code 126and "Request interrupted by user for tool use" (no actual user interruption occurs) whensandbox.enabled: trueis set with a non-trivialfilesystem.denyReadlist and/orpermissions.deny Read(...)patterns.Root cause (confirmed via strace): Claude Code constructs a
bubblewrap(bwrap) sandbox wrapper command and passes it as a single-cargument to/bin/bash. This single argument exceeds Linux'sMAX_ARG_STRLEN = PAGE_SIZE × 32 = 128 KBlimit (defined ininclude/uapi/linux/binfmts.h), causingposix_spawnto returnE2BIG.Important: This is NOT the
ARG_MAX(2 MB) limit. The kernel enforces a separate per-argument size cap viaMAX_ARG_STRLEN.Baseline checks (all normal — ruling out environmental causes):
getconf ARG_MAX= 2097152ulimit -s= 8192environsizes are in the single-digit KB rangebash -c 'echo hello'worksnode -e "child_process.spawn('bash', ['-c', 'echo hello'])"worksIndependent verification that narrowed this to the harness/bwrap layer:
claude --dangerously-skip-permissions, same underlyingclaudebinary) reproduce the failure identically.plan/default/acceptEdits/ bypass all fail to resolve it.posix_spawnfailure.What Should Happen?
Bash tool calls should execute regardless of how many sandbox deny/allow rules the user has configured. The bwrap invocation should not hit kernel argument length limits.
Suggested fixes (in order of preference):
bwrapdirectly with an argv array instead of wrapping it in/bin/bash -c "<string>". The current design forces the entire bwrap configuration into a single argument.--args-fd/--args <file>/ stdin so the sandbox policy does not compete with argv space.Read(//**/foo)andRead(**/foo)pattern pairs inpermissions.denyappear to each expand into their own--tmpfsentries.HTTP_PROXY,HTTPS_PROXY,ALL_PROXY,FTP_PROXY,GIT_SSH_COMMAND,DOCKER_*,CLOUDSDK_PROXY_*,GRPC_PROXY, etc.) — these contribute ~25--setenventries per Bash call regardless of user setting.Error Messages/Logs
Shape of the failing execve (sanitized; proxy env vars and internal mounts summarized):
Captured with:
Steps to Reproduce
Use a recent WSL2 kernel on Windows.
In
~/.claude/settings.json, enable sandbox with a realistic deny configuration. Minimal reproducer uses roughly this shape — the total rule count across both sections matters more than specific paths:{ "permissions": { "deny": [ "Read(//**/.env)", "Read(**/.env)", "Read(//**/.env.local)", "Read(**/.env.local)", "Read(//**/*.pem)", "Read(**/*.pem)", "Read(//**/*.key)", "Read(**/*.key)", "Read(//**/secrets/**)", "Read(**/secrets/**)" // ... total ~30 patterns typical of a conservative production config ] }, "sandbox": { "enabled": true, "filesystem": { "denyRead": [ "**/.env", "**/.env.*", "**/secrets/**", "**/*.pem", "**/*.key" // ... plus typical host-level path patterns ], "allowRead": [ "**/.env.example", "**/.git/config" ] } } }Start a fresh Claude Code session (VS Code extension or CLI:
claude --dangerously-skip-permissions).Send any message that invokes the Bash tool, e.g.
run echo hello.Observe: the tool call fails immediately with
Exit code 126and[Request interrupted by user for tool use].Minimal workaround confirmed: collapsing
sandbox.filesystem.denyReadto a single broad entry (e.g.["~/"]) with narrowallowReadcarve-outs for the binaries/caches/paths actually needed reduces bwrap argv below the 128 KB threshold and restores Bash tool functionality.Claude Model
Opus
Is this a regression?
I don't know
Claude Code Version
2.1.114 (Claude Code)Platform
Anthropic API
Operating System
Other Linux
Terminal/Shell
WSL (Windows Subsystem for Linux)
Additional Information
Environment (minimal)
getconf PAGE_SIZE=4096, soMAX_ARG_STRLEN ≈ 131072 bytes (128 KB)Hypothesis on argument-length growth
The failing command string consists mostly of three categories:
--setenv(~25 entries) — auto-injected by Claude Code, not user-configurable. Roughly 4-6 KB.--bind/--ro-bind(6-8 entries) — workspace + Claude's own runtime sockets / debug paths. Not user-adjustable.--tmpfsper denied path — this is the dominant growth factor. Bothpermissions.deny Read(...)patterns andsandbox.filesystem.denyReadentries expand here, apparently without deduplication.Users who enable a conservative sandbox with explicit denylists routinely have enough deny patterns (30+ patterns across
permissions.denyandsandbox.filesystem.denyRead) to exceed the 128 KB threshold.Related issues
sandbox.enabled: falsebeing ignored in 2.1.79Other tools for comparison
Severity
Medium-High. Users with realistic
permissions.deny/sandbox.filesystem.denyReadconfigurations hit this on every Bash tool call, which blocks any shell-dependent workflow. The user-side workaround (replacing granular deny patterns with a single broad deny plus narrowallowReadcarve-outs) resolves the symptom but reduces isolation granularity.