File descriptor leak: settings.json opened but never closed on each tool call
Summary
Claude Code leaks one file descriptor per tool invocation by opening ~/.claude/settings.json and never closing it. Over a normal session the count grows unboundedly until the system-wide file table is exhausted (ENFILE: file table overflow), destabilizing the entire OS (other apps fail to open windows/files).
Environment
- OS: macOS 15 (Sequoia), Apple Silicon
- Claude Code versions observed: 2.1.53, 2.1.56, 2.1.58 (Cursor extension), 2.1.59 (CLI)
- Install methods: native CLI (
~/.local/bin/claude) and Cursor extension - Shell: zsh
Reproduction
No special project configuration is required. The leak occurs in every Claude Code session regardless of project.
- Start Claude Code (CLI or Cursor extension) in any project directory.
- Use it normally — each tool call (Bash, Read, Glob, Grep, Edit, Write, etc.) leaks ~1 FD.
- Monitor with:
lsof -nP -p <PID> | grep settings.json | wc -l - The count increases monotonically and never decreases.
Measured growth rate (live session, Cursor extension v2.1.58)
| Time | settings.json FDs | Event |
|------|-------------------|-------|
| T+0 | 5,346 | Baseline (idle — no growth) |
| T+5s | 5,348 | After 1× Bash tool call (+2) |
| T+11s | 5,349 | After 1× Bash tool call (+1) |
| T+18s | 5,351 | After 2× parallel tool calls (Bash + Glob) (+2) |
| T+37s | 5,355 | After several more tool calls (+4) |
Idle periods show zero growth — the leak is strictly correlated with tool dispatch, not a timer/watcher.
Worst-case observation (CLI v2.1.53)
A single claude process accumulated ~49,900 open handles to the same file:
$ lsof -nP -p <PID> | awk 'NR>1{print $NF}' | sort | uniq -c | sort -nr | head
49902 ~/.claude/settings.json
6 /dev/ttys001
...
$ lsof -nP -p <PID> | awk 'NR>1{print $NF}' | sort -u | wc -l
89 # only 89 unique paths
Impact
- System-wide: Once the leaked FDs approach
kern.maxfiles(default 122,880 on macOS), other applications (Chrome, Finder, etc.) fail withENFILEor behave erratically. - Claude Code: Settings reads/writes fail, breaking preferences and permission checks.
- Recovery: Requires killing the Claude Code process.
Root cause hypothesis
Every tool call (or permission check before a tool call) opens ~/.claude/settings.json for reading but the file descriptor is never closed. This is consistent with:
- Leak count ≈ total tool invocations in the session
- Only
REG(regular file) type FDs accumulate; no sockets involved - The file is 40+ KB (grows with number of configured projects), but the leak occurs regardless of file size
TYPE breakdown of the leaking process
5376 REG
32 DIR
27 unix
4 KQUEUE
1 systm
1 NPOLICY
Conditions that accelerate the leak
These are not required to trigger the bug, but make it hit the limit faster:
- Long sessions — more tool calls = more leaked FDs
- Frequent Bash calls with PostToolUse hooks — each hook execution may trigger additional internal settings reads
- Subagent/Task tool usage — spawns additional tool calls
- Large
settings.json— 16+ project entries inflate the file to 40+ KB (does not cause the leak, but may be relevant to the read pattern)
Workaround
- Periodically restart Claude Code sessions to release leaked FDs.
- Monitor FD growth:
``bash``
pgrep -f claude | while read pid; do
c=$(lsof -nP -p "$pid" 2>/dev/null | awk '$NF ~ /settings\.json$/ {n++} END{print n+0}')
echo "PID=$pid settings.json=$c"
done
- Reduce session length when performing many tool calls.
Expected behavior
~/.claude/settings.json should be opened and closed on each read. The number of open FDs to this file should remain constant (≤ 2–3) regardless of session length or tool call count.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗