claude-code - 💡(How to fix) Fix Bundled bfs find rewrite holds ~65k DIR fds per call on large monorepos, contributes to ENFILE [1 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#56091Fetched 2026-05-05 05:58:28
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Timeline (top)
labeled ×4

Claude Code's shell snapshot rewrites every find invocation to its bundled bfs binary (breadth-first find). On large monorepos this single design choice causes large, deterministic spikes in kern.num_files because bfs opens a directory file descriptor for every directory in its current frontier and does not release them until the walk completes. On the monorepo I tested it on (~65,000 directories), one find call holds 65,532 DIR file descriptors simultaneously.

This is one concrete, reproducible source of the broader FD-exhaustion behavior tracked in #47909.

Root Cause

Claude Code's shell snapshot rewrites every find invocation to its bundled bfs binary (breadth-first find). On large monorepos this single design choice causes large, deterministic spikes in kern.num_files because bfs opens a directory file descriptor for every directory in its current frontier and does not release them until the walk completes. On the monorepo I tested it on (~65,000 directories), one find call holds 65,532 DIR file descriptors simultaneously.

Fix Action

Fix / Workaround

Workarounds (for users hitting this today)

Code Example

find () {
    local _cc_bin="${CLAUDE_CODE_EXECPATH:-}"
    [[ -x $_cc_bin ]] || _cc_bin=$(command -v claude 2>/dev/null)
    if [[ ! -x $_cc_bin ]]; then
        command find "$@"
        return
    fi
    if [[ -n $ZSH_VERSION ]]; then
        ARGV0=bfs "$_cc_bin" -regextype findutils-default "$@"
    elif ...
    elif [[ $BASHPID != $$ ]]; then
        exec -a bfs "$_cc_bin" -regextype findutils-default "$@"
    else
        ( exec -a bfs "$_cc_bin" -regextype findutils-default "$@" )
    fi
}

---

find <repo-root> -name SOME_FILENAME

---

sysctl kern.num_files
lsof | awk 'NR>1 {print $1, $2}' | sort -u | awk '{cnt[$2]++} END {for (k in cnt) print cnt[k], k}' | sort -rn | head

---

PID 38229  bfs -regextype findutils-default /Users/<user>/Projects/<monorepo> -name catalog-tool
   65,540 FDs total
   65,532 of which are DIR

kern.num_files: 74,450  (baseline ~8,800; Δ +65,650 from this single call)

Top file-type breakdown at peak:
   41,960 DIR      ← almost entirely from the bfs frontier
   10,905 REG
    1,492 CHR
      174 IPv4
      173 unix
       25 IPv6
RAW_BUFFERClick to expand / collapse

Summary

Claude Code's shell snapshot rewrites every find invocation to its bundled bfs binary (breadth-first find). On large monorepos this single design choice causes large, deterministic spikes in kern.num_files because bfs opens a directory file descriptor for every directory in its current frontier and does not release them until the walk completes. On the monorepo I tested it on (~65,000 directories), one find call holds 65,532 DIR file descriptors simultaneously.

This is one concrete, reproducible source of the broader FD-exhaustion behavior tracked in #47909.

The shell function

~/.claude/shell-snapshots/snapshot-zsh-*.sh (regenerated each session) installs:

find () {
    local _cc_bin="${CLAUDE_CODE_EXECPATH:-}"
    [[ -x $_cc_bin ]] || _cc_bin=$(command -v claude 2>/dev/null)
    if [[ ! -x $_cc_bin ]]; then
        command find "$@"
        return
    fi
    if [[ -n $ZSH_VERSION ]]; then
        ARGV0=bfs "$_cc_bin" -regextype findutils-default "$@"
    elif ...
    elif [[ $BASHPID != $$ ]]; then
        exec -a bfs "$_cc_bin" -regextype findutils-default "$@"
    else
        ( exec -a bfs "$_cc_bin" -regextype findutils-default "$@" )
    fi
}

So every find call inside the Claude Code Bash tool routes through the bundled claude binary acting as bfs.

Reproduction

Environment: macOS 15.4 (Darwin 25.4.0), zsh, Claude Code 2.1.118, kern.maxfiles=491520, kern.maxfilesperproc=245760.

In a Claude Code session inside any large monorepo (mine has ~65k directories):

find <repo-root> -name SOME_FILENAME

While that runs, in another terminal:

sysctl kern.num_files
lsof | awk 'NR>1 {print $1, $2}' | sort -u | awk '{cnt[$2]++} END {for (k in cnt) print cnt[k], k}' | sort -rn | head

Evidence (lsof snapshot during peak)

PID 38229  bfs -regextype findutils-default /Users/<user>/Projects/<monorepo> -name catalog-tool
   65,540 FDs total
   65,532 of which are DIR

kern.num_files: 74,450  (baseline ~8,800; Δ +65,650 from this single call)

Top file-type breakdown at peak:
   41,960 DIR      ← almost entirely from the bfs frontier
   10,905 REG
    1,492 CHR
      174 IPv4
      173 unix
       25 IPv6

When the walk completes, all 65,532 DIR fds release simultaneously (verified — monitor recorded Δ-65,539 the moment the process exited).

Why this is bad

  1. System-wide ENFILE. kern.maxfiles=491,520 — only ~7 concurrent walks on a tree this size will exhaust the system file table. Cable-lens, cable, or any flow that fans out parallel Bash subagents each running find will rapidly hit this.
  2. Cascading misleading errors. Once the FD ceiling is hit, the symptoms surface as the misleading errors documented in #47909 — ENOENT on posix_spawn 'rg', "directory no longer exists" on Bash cwd, etc.
  3. Silent. Users running Claude Code in monorepos have no visibility that find is the trigger — they invoked a normal-looking shell command.
  4. Counterintuitive. Native macOS BSD find and GNU find are depth-first / streaming — they never hold this many DIR fds. The bfs substitution is what makes the cost qualitatively different.

Suggested fixes

In rough priority order:

  1. Cap concurrent open DIR fds in the bundled bfs. A configurable upper bound on the frontier (e.g. close + reopen LRU descriptors) would prevent unbounded growth on huge trees with little change to user-visible behavior.
  2. Expose an opt-out env var like CLAUDE_CODE_DISABLE_FIND_REWRITE=1 — when set, the shell function should command find "$@" and return.
  3. Make the rewrite skip large trees. A heuristic check (e.g., if the target path contains > N top-level directories) that falls back to system find.
  4. Document the rewrite. Currently invisible to users; surprising when it manifests. A note in the shell-snapshot file or in the docs would help users understand why their find calls behave differently.

Workarounds (for users hitting this today)

  • Use git ls-files | grep <pattern> for tracked-file searches in repos
  • Use rg --files <path> | grep <pattern> for streaming file enumeration
  • Prefix with command (command find /path -name X) to bypass the function
  • Add -maxdepth N to cap the walk

Related

  • #47909 — same underlying FD exhaustion; this issue identifies one concrete cause

extent analysis

TL;DR

The most likely fix is to cap concurrent open DIR fds in the bundled bfs to prevent unbounded growth on huge trees.

Guidance

  • Identify the bfs binary and its current behavior of opening a directory file descriptor for every directory in its current frontier.
  • Consider implementing a configurable upper bound on the frontier to prevent excessive file descriptor usage.
  • As a temporary workaround, users can prefix their find commands with command to bypass the bfs substitution.
  • Another workaround is to use alternative commands like git ls-files | grep <pattern> or rg --files <path> | grep <pattern> for file searches.

Example

No code snippet is provided as the issue is more related to the behavior of the bfs binary and its interaction with the system's file descriptor limit.

Notes

The provided workarounds may not be suitable for all use cases, and a more permanent fix would involve modifying the bfs binary or the shell function that rewrites find invocations.

Recommendation

Apply a workaround, such as prefixing find commands with command or using alternative file search commands, until a more permanent fix is available. This is because the current behavior of the bfs binary can lead to system-wide ENFILE errors and cascading misleading errors.

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 Bundled bfs find rewrite holds ~65k DIR fds per call on large monorepos, contributes to ENFILE [1 participants]