[BUG] Bash tool fails with /proc/self/fd/3: Permission denied after a few calls in 2.1.116 and 2.1.117
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?
The Bash tool succeeds on a small number of early calls per session, then transitions to a permanent-for-the-session failure where every subsequent Bash call — including a bare true — exits 126 with /bin/bash: line 4: /proc/self/fd/3: Permission denied. Once the failure starts in a session, it never recovers; only starting a new Claude Code process gets the small initial window of working calls back. The pattern reproduces in every session on 2.1.116 and 2.1.117 and is independent of command shape.
What Should Happen?
The Bash tool should run the requested command and return its output. If the command fails for its own reasons (e.g. a real git error), that output should be returned. The wrapper preamble should not block execution by failing to open /proc/self/fd/3.
Error Messages/Logs
Exit code 126
/bin/bash: line 4: /proc/self/fd/3: Permission denied
- Exit 126 = bash "permission denied — could not exec target."
- "line 4" is constant across every observed instance and is almost certainly inside an injected wrapper preamble (most user-supplied commands are a single line).
- The failing reference is /proc/self/fd/3 — whatever bash inherited as fd 3.
- Failure is independent of the user command (single command, compound with && / |, git, non-git, bare true).
Steps to Reproduce
- Install Claude Code 2.1.116 or 2.1.117:
npm install -g @anthropic-ai/claude-code@2.1.116 - Start a fresh Claude Code session in a WSL2 shell with the sandbox enabled.
- Issue a few trivial Bash tool calls (e.g.,
ls,pwd,echo). These succeed. - Issue a slightly heavier call. In the most recent reproduction, the fifth call —
cd /some/dir && git rev-parse --is-inside-work-tree 2>&1— tripped the failure. - From that point on, every Bash call in the session fails with the same
/proc/self/fd/3: Permission deniederror, including a baretrue. - Confirmed remediation: downgrade to 2.1.114 (
npm install -g @anthropic-ai/claude-code@2.1.114). Same reproducer is then clean.
Trigger pattern observed in the most recent test (2.1.116):
- Calls 1–4 (parallel
ls,pwd && whoami && uname -a,echochain) → all succeeded. - Call 5 (
cd … && git rev-parse) → tripped the failure. - Bare
trueimmediately after → also failed.
Whether the trigger is tied to call count, to git invocation specifically, or to some other property of the fifth call has not been bisected.
The following do not restore the Bash tool: process restart, machine reboot, the prompt !cmd prefix (!cmd traverses the same Bash tool path as agent-issued tool calls, not the user's underlying terminal).
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
2.1.114 (2.1.115 unavailable in npm)
Claude Code Version
2.1.117 (also reproduced on 2.1.116)
Platform
Anthropic API
Operating System
Ubuntu/Debian Linux
Terminal/Shell
Windows Terminal
Additional Information
Hypothesis: kernel "dumpable" flag + wrapper accessing /proc/self/fd/3 inside a bwrap sandbox
Best fit for the observed behavior:
- The Bash tool wraps user commands in a bash script. Its preamble references
/proc/self/fd/3on line 4 — plausibly to source the user command from a pipe held open as fd 3 by the parent (source /proc/self/fd/3,exec < /proc/self/fd/3, or similar). - The harness sandbox is bubblewrap (bwrap). Bwrap creates sandboxes via Linux user namespaces and can also drop capabilities or perform setuid-style transitions. Any of these clears the kernel's
PR_SET_DUMPABLEflag on the bash subprocess. - With
dumpable = 0,/proc/<pid>/*is owned by root with restrictive permissions, and the bash subprocess cannot open/proc/self/fd/3. The open returnsEACCESand bash exits 126 before running any user code.
A 2.6.22 kernel patch relaxed this for fd specifically, but the restriction still applies in containers, with certain capability changes, and with some syscalls (e.g. close_range).
This explains every observed symptom:
- Per-session reproduction: the dumpable flag is per-process; each new bash invocation hits it fresh.
- Identical "line 4" failure across every session: the same wrapper preamble runs every time.
- Works for a few calls then breaks: plausibly two code paths in the Bash tool — a fast/warm path that does not drop privs, and a fuller path that does. Crossing into the second path is what breaks the session. Alternatively the early calls do not exercise the line-4 access (an early-return on a fast path); the first call that does is the one that visibly fails.
- Survives reboot: the cause is wrapper code + sandbox-launch path, both of which load fresh in every new Claude Code process.
- Began at 2.1.115 or 2.1.116: one of those two releases changed either the wrapper preamble or the bwrap-launch path in a way that exposes the dumpable interaction. 2.1.117 inherits the same bug.
I cannot directly inspect the wrapper preamble or the bwrap-launch path from inside the agent, so this is a strongly-supported hypothesis, not a verified diagnosis.
Open questions
- The exact contents of the Bash tool's wrapper preamble, and how line 4 accesses
/proc/self/fd/3(source,exec <,cat, etc.). - Which specific bwrap operation in the launch path clears the dumpable flag (user namespace creation, capability drop, setuid transition, etc.).
- Why 2.1.115 is missing from npm. It may have been unpublished intentionally, or never published. If it was pulled because of this regression, that would identify 2.1.115 as the bad release and 2.1.116 as inheriting the bug rather than introducing it.
- What changed between 2.1.114 and 2.1.116 on the wrapper preamble and/or bwrap-launch path.
Related but distinct
#51126 — "[BUG] WSL2: Bash tool fails with E2BIG because Claude wraps bubblewrap in single /bin/bash -c string exceeding Linux
MAX_ARG_STRLEN"
Same WSL2 platform and same bwrap sandbox layer, but a distinct failure mode and distinct affected versions:
- #51126 fails at
posix_spawnwithE2BIG(argv > 128 KB), before bash ever runs. - This issue fails inside bash on line 4 of an injected wrapper preamble, with
EACCESon/proc/self/fd/3. - #51126 is reported on 2.1.114, which is clean for this bug.
- This bug is bounded to 2.1.115 or 2.1.116 (and inherited by 2.1.117).
- #51126 only manifests when the bwrap deny-list is large enough to push the single
-cargument past 128 KB; this bug triggers regardless of deny-list size, after a small number of calls.
Both likely stem from the bwrap-based sandbox layer's interaction with the existing Bash-tool wrapper, but they are independent failure modes triggered under different conditions.
Observed sessions
Session A
Pre-break (succeeded):
ls -la /home/grault/planning/ 2>&1 | head -50
ls -la /home/grault/planning/applications/ 2>&1 | head -20
Then everything subsequent failed identically:
cd /home/grault/planning && git rev-parse --is-inside-work-tree 2>&1
ls -la /home/grault/planning/.git 2>&1 | head -3
test -d /home/grault/planning/.git && echo "git repo" || echo "not a git repo"
Session B
Last working call: git add .claudeignore && git commit -m ... → returned [main 8521f16] chore(config): add .claudeignore.
First failing call (immediately after):git log --oneline -2 && echo --- && git status --short && echo --- && wc -l CLAUDE.md .claudeignore.
Every Bash call since, including a bare true, has failed with the same fd-3 error.
Sources for the kernel mechanism
proc(5)man page on/proc/[pid]/fd/and the dumpable attribute.- Red Hat solution 36870, "A setuid root program is unable to access
/proc/self/fdafter it lowers its privileges to a normal user." - LKML thread "PROBLEM?: 'permission denied' when accessing
/proc/self/fd/*after setuid" (kernel patch in 2.6.22). - Stack Overflow question 75558566, "Why is
/proc/self/fd/nforbidden after setuid?" - containers/podman issue 10337, "Permission denied when container process executes
close_rangesyscall" — same kernel mechanism in a container context.``
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗