File descriptor leak: settings.json opened but never closed on each tool call

Resolved 💬 3 comments Opened Feb 26, 2026 by fujikkofujio Closed Mar 1, 2026

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.

  1. Start Claude Code (CLI or Cursor extension) in any project directory.
  2. Use it normally — each tool call (Bash, Read, Glob, Grep, Edit, Write, etc.) leaks ~1 FD.
  3. Monitor with: lsof -nP -p <PID> | grep settings.json | wc -l
  4. 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 with ENFILE or 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:

  1. Long sessions — more tool calls = more leaked FDs
  2. Frequent Bash calls with PostToolUse hooks — each hook execution may trigger additional internal settings reads
  3. Subagent/Task tool usage — spawns additional tool calls
  4. 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.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗