claude-code - 💡(How to fix) Fix [Bug] File descriptor leak causes ENOENT on subprocess spawn, "directory no longer exists" on Bash cwd [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#47909Fetched 2026-04-15 06:38:54
View on GitHub
Comments
1
Participants
2
Timeline
6
Reactions
0
Timeline (top)
labeled ×4commented ×1cross-referenced ×1

Error Message

error: An unknown error occurred, possibly due to low max file descriptors (Unexpected) Current limit: 256

To fix this, try running: ulimit -n 2147483646

Root Cause

TL;DR — one root cause, three surfaced symptoms

Fix Action

Fix / Workaround

Every structured code search required me to substitute a Bash call with grep -n .... Not a blocker, but:

  • Tool-use budget wasted on retries
  • The Grep tool's advertised features (multiline, type filters, context lines) were unavailable even though system grep would have covered most of them
  • The error text does not suggest the workaround or mention a missing dependency — it just surfaces a raw syscall error

Code Example

error: An unknown error occurred, possibly due to low max file descriptors (Unexpected)
Current limit: 256

To fix this, try running:
  ulimit -n 2147483646

---

Grep(pattern="prepCodeIds:\\s*skel", path=".../ticketProductionTrigger.ts", output_mode="content")

---

ENOENT: no such file or directory, posix_spawn 'rg'

---

Working directory "/../../../../../functions" no longer exists. Please restart Claude from an existing directory.

---

error: An unknown error occurred, possibly due to low max file descriptors (Unexpected)

Current limit: 256

To fix this, try running:

  ulimit -n 2147483646

If that still doesn't work, you may need to run:

  sudo launchctl limit maxfiles 2147483646
RAW_BUFFERClick to expand / collapse

Claude Code — Bug Report

Environment

  • Product: Claude Code CLI
  • Model: claude-opus-4-6 (1M context)
  • OS: macOS (Darwin 25.4.0)
  • Shell: zsh
  • Repo: Next.js + Firebase Cloud Functions monorepo (path: ~/Documents/GitHub/Acme)
  • Session type: interactive, plan-mode → execute; resumed via claude --resume <session>

TL;DR — one root cause, three surfaced symptoms

Claude Code exhausted the process's file-descriptor limit (macOS default soft limit: 256) during a long interactive session. The fd exhaustion surfaced as three different misleading errors across the session, none of which pointed at the actual cause until a session resume finally produced:

error: An unknown error occurred, possibly due to low max file descriptors (Unexpected)
Current limit: 256

To fix this, try running:
  ulimit -n 2147483646

A machine restart cleared the leaked fds and everything worked normally again. The three symptoms below are all consistent with running out of fds inside the same process; filing them as one report because they should be fixed together.


Symptom 1 — Grep tool fails with ENOENT: posix_spawn 'rg'

What I called

Grep(pattern="prepCodeIds:\\s*skel", path=".../ticketProductionTrigger.ts", output_mode="content")

Actual result

ENOENT: no such file or directory, posix_spawn 'rg'

The same error returned for every Grep invocation, not just this one.

Expected result

Matching lines from the file (ripgrep-equivalent behavior).

Root cause (revised after fd-exhaustion diagnosis)

Almost certainly not a missing binary. posix_spawn requires several new file descriptors (pipes for stdin/stdout/stderr plus internal bookkeeping). When the process is already near its soft fd limit, posix_spawn can fail for lack of fds and the syscall's failure mode gets reported by the tool runtime as a generic ENOENT. The error surface string ("no such file or directory") is the default libc message for multiple errno values in this codepath and is very misleading.

Evidence:

  • rg failures appeared ~30 minutes into the session, not at session start
  • Other Bash subprocess spawns (grep, npx tsc, npm test) continued to work — suggests the fd pool was marginal, not fully empty, and rg happened to need one more fd than was available at that moment
  • Hours later the session produced the explicit "low max file descriptors" message, confirming the process had been near its ceiling

Impact

Every structured code search required me to substitute a Bash call with grep -n .... Not a blocker, but:

  • Tool-use budget wasted on retries
  • The Grep tool's advertised features (multiline, type filters, context lines) were unavailable even though system grep would have covered most of them
  • The error text does not suggest the workaround or mention a missing dependency — it just surfaces a raw syscall error

Suggested fix

  1. Detect missing rg at tool-call time and return a clearer error ("ripgrep not found on PATH; please install ripgrep or use Bash grep as a fallback").
  2. Optionally fall back to the system grep for basic output_mode: "content" | "files_with_matches" cases so the tool degrades gracefully.
  3. At minimum, document the ripgrep dependency in the Grep tool description so callers know to install it.

Symptom 2 — Persistent Bash shell state claims cwd "no longer exists"

This one was more disruptive.

What I called

After running several commands with cd /../../../../functions && <cmd> successfully, the next Bash call — a plain npx tsc --noEmit — failed:

Working directory "/../../../../../functions" no longer exists. Please restart Claude from an existing directory.

What I did to confirm the directory still existed

  1. Called cd /../../../../Acme && pwdsame error.
  2. Called pwd alone — same error.
  3. The user opened their own bash prompt and ran cd ..same error (Working directory "/../../../../../functions" no longer exists).
  4. After the session was interrupted and resumed, I called cd /../../../../../functions && npx tsc --noEmit → it worked, exit 0. The directory clearly existed the whole time.

What triggered it

I cannot reproduce deterministically. It happened after:

  • ~8 minutes of continuous work in that cwd
  • Many back-to-back Edit / Write / Bash calls
  • One Write call that created a new file (functions/scripts/auditUnivAssemblies.ts) immediately before the first failure

No external process touched the filesystem (no rm, no editor, no VCS ops, no emulator restart).

Observation

The persistent Bash shell state is the thing that got stuck. The directory on disk was fine. Once I stopped relying on the session's tracked cwd and prefixed every command with cd /absolute/path && <cmd>, the tool started working again — but only because each command was effectively spawning fresh shell state. Any call that needed to execute in the sticky cwd still failed with the same error.

Root cause (revised after fd-exhaustion diagnosis)

The Bash tool's persistent shell tracks cwd via an open file descriptor (standard pattern — openat-style tracking so cwd semantics survive renames). When the process hits its fd ceiling:

  • Any syscall that needs a new fd (stat, open, readdir, even re-opening the cwd fd) fails
  • The shell wrapper treats that failure as "directory vanished" and surfaces "no longer exists"
  • The directory on disk was never affected — only the shell's ability to re-open or verify it

This explains why every Bash call failed once fd exhaustion tipped over, and why the issue persisted across absolute-path cd attempts (new commands still needed fds that weren't available).

Impact

Worse than Bug 1. I lost the ability to run any Bash command that relied on sticky state. The error message actively misleads the user — telling them to restart Claude when the directory is demonstrably still there. Running rm -rf or any recovery attempt based on that error text would be dangerous.

Suggested fix

  1. Do not tell the user to restart Claude or that a directory "no longer exists" without first distinguishing an ENOENT from an EMFILE/ENFILE at the syscall level. On macOS the errnos are distinct; surface them accurately.
  2. If fd pressure is detected (via getrlimit or repeated EMFILE), surface the actual diagnosis immediately — not hours later on session resume.
  3. Add a one-line recovery hint: "try prefixing your command with 'cd /absolute/path &&' to bypass the sticky cwd" — but only as a stopgap; the real fix is to not leak fds.

Symptom 3 — On claude --resume, "low max file descriptors" error

After the interactive session was interrupted, attempting to resume produced:

error: An unknown error occurred, possibly due to low max file descriptors (Unexpected)

Current limit: 256

To fix this, try running:

  ulimit -n 2147483646

If that still doesn't work, you may need to run:

  sudo launchctl limit maxfiles 2147483646

Issues with this error

  1. The suggested ulimit -n 2147483646 is wrong on macOS. macOS caps per-process fds well below 2^31. Realistic hard limits on macOS are OPEN_MAX (~10240 or configurable via launchctl limit maxfiles) — asking users to run a value that large will either fail or silently clamp. The suggested command is misleading.
  2. The message conflates two different limits. ulimit -n adjusts the shell's soft limit for child processes; it does not raise the limit for an already-running process (which is what was actually exhausted). For a resumed claude process the advice cannot fix the issue without a restart.
  3. This is the first time in the session the user got an accurate diagnosis. The same underlying condition had been surfacing as "ripgrep not found" and "directory no longer exists" for the preceding ~30 minutes.

The user ultimately restarted the entire machine and everything returned to normal — which is consistent with a per-process fd leak that doesn't clear until the process dies.

Suggested fix

  1. Check getrlimit(RLIMIT_NOFILE) at session start and warn if the soft limit is at macOS default (256) before heavy tool use begins.
  2. Suggest realistic values: ulimit -n 10240 on macOS, and sudo launchctl limit maxfiles 65536 200000 for system-wide. The literal 2147483646 is not a helpful recommendation.
  3. Audit Claude Code for fd leaks — persistent Bash shells, long-running watchers, MCP server connections, and any file-descriptor-based caches are all candidates. A ~45-minute session with ~100 tool calls should not approach 256 fds unless something is leaking.
  4. Consider periodically logging fd count in debug builds so support can correlate the leak with specific tool types.

Combined pattern

All three symptoms trace to a single root cause: the Claude Code process is leaking file descriptors during long interactive sessions, and the tool runtime surfaces the exhaustion as three different misleading errors (ENOENT on subprocess spawn, "directory no longer exists" on cwd access, "unknown error" on session resume) before it eventually points at the actual problem. In a subagent or long autonomous run, any of these would cause the agent to spiral or give up on a task that is otherwise completable.

The most important fixes, in priority order:

  1. Find and fix the fd leak. 256 fds should be plenty for a tool-using agent session; hitting the ceiling means something isn't closing.
  2. Surface the right error. If EMFILE/ENFILE occurs, say so — don't route it through error codepaths that print ENOENT-style text.
  3. Fix the remediation advice. ulimit -n 2147483646 on macOS is not correct.

Session metadata (if useful to support)

  • Plan file: ~/.claude/plans/sprightly-painting-willow.md
  • Project CLAUDE.md references Vitest, targeted Firebase deploys, functions/src/shared/ sync behavior
  • All three symptoms reproduced during implementation of Sub-Plans 1–4 from that plan
  • Session duration before first fd-related symptom: ~30 minutes
  • Session duration before "low max file descriptors" message: ~45–60 minutes
  • User remediation: full machine restart (reopening the terminal or restarting claude alone was not sufficient to clear the condition)
  • Final outcome (after restart): all edits landed, 100/100 vitest tests pass, tsc clean on both functions/ and site/

extent analysis

TL;DR

The most likely fix is to increase the file descriptor limit and address the file descriptor leak in the Claude Code process.

Guidance

  • Identify and fix the file descriptor leak in the Claude Code process to prevent exhaustion of the file descriptor limit.
  • Increase the file descriptor limit using a realistic value, such as ulimit -n 10240 on macOS, to prevent the process from hitting the ceiling.
  • Improve error handling to surface the correct error message when a file descriptor limit is reached, rather than routing it through error codepaths that print misleading text.
  • Update the remediation advice to suggest realistic values for increasing the file descriptor limit.

Example

No specific code snippet is provided, but the issue suggests that the Claude Code process is leaking file descriptors, which can be addressed by reviewing the code for persistent file descriptors, such as those used by persistent Bash shells, long-running watchers, or file-descriptor-based caches.

Notes

The issue is specific to the Claude Code process and its interaction with the operating system's file descriptor limit. The suggested fixes are tailored to the macOS environment, where the file descriptor limit is capped at a lower value than on other systems.

Recommendation

Apply a workaround by increasing the file descriptor limit using ulimit -n 10240 on macOS, and prioritize finding and fixing the file descriptor leak in the Claude Code process to prevent similar issues in the future.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [Bug] File descriptor leak causes ENOENT on subprocess spawn, "directory no longer exists" on Bash cwd [1 comments, 2 participants]