[BUG] VSCode extension: fd leak from session transcript scanning causes "Error: spawn EBADF" after hours of uptime
Description
The VSCode extension's host process leaks file descriptors for Claude session transcripts. On every periodic rescan of ~/.claude/projects/, the extension opens every .jsonl transcript in every project directory and never closes the handles. After a few hours of uptime the extension host holds thousands of fds, and from that point every attempt to spawn the bundled claude binary fails with Error: spawn EBADF. Both new session launches and the claude auth status probe in refreshCliAuthStatus fail. Already-running sessions keep working. Restarting VSCode or the extension host clears the fd table and everything works again until the leak reaccumulates.
Environment
- Claude Code VSCode extension: 2.1.220 (current marketplace latest, bundled native binary)
- macOS, Apple Silicon
- A few hundred session transcripts on disk in
~/.claude/projects/(ordinary daily-driver usage)
Evidence
Measured on a live extension host a few hours after start, with multiple Claude panes open:
- 4,349 open fds total, of which 4,281 were regular files and ~4,200 were transcripts under
~/.claude/projects/(lsof -p <exthost pid>). Highest fd number in use: 4313. - Every transcript file was held open exactly 25 times, matching 25 scans since the extension host started (roughly one scan every 7 to 8 minutes). Per-directory totals match file count on disk times scan count; for example, a project directory with 82 transcripts on disk accounted for 1,991 open fds (82 x 25, minus files created after the early scans).
- Zero EMFILE anywhere in the logs. The failure is EBADF at spawn time, consistent with macOS
posix_spawnfd-table setup failing once the parent's fd numbers are in the thousands, rather than classic fd-count exhaustion. - Failure onset correlates with uptime, not with any specific action: in one app run the first EBADF appeared roughly five hours after the window opened, and failures then recurred until restart. Short-lived windows never hit it.
Log excerpt (Claude VSCode.log, timestamps and channel ids omitted):
[info] Launching Claude on channel: <id>
[info] Spawning Claude with SDK query function - cwd: /Users/<user>, version: 2.1.220
[error] Error spawning Claude (on channel <id>): Error: spawn EBADF
[error] Failed to load config cache: Error: spawn EBADF
[error] Error processing client request: Error: spawn EBADF
Stack trace (exthost.log):
Error: spawn EBADF
at ChildProcess.spawn (node:internal/child_process:441:11)
at Object.spawn (node:child_process:829:9)
at extension.js:335:19611
at go.refreshCliAuthStatus (extension.js:335:19584)
at go.getAuthStatus (extension.js:335:19208)
at go.processRequest (extension.js:335:27923)
Reproduction
- Accumulate a few hundred session transcripts in
~/.claude/projects/(normal daily use gets there quickly). - Open a VSCode window with the Claude Code extension and use a session or two.
- Watch
lsof -p <extension host pid> | grep -c "/.claude/projects/"climb by (number of transcripts on disk) every few minutes and never go down. - After a few hours, launching any new Claude pane fails immediately with
spawn EBADFuntil the extension host restarts.
Notes
- Possibly related to #62735 (closed as stale). That report ruled out fd exhaustion on a fresh process; this one is specifically the extension host accumulating transcript fds over hours, which explains the "works after restart, breaks after ~30+ minutes" pattern many users describe.
- Workarounds that follow from the root cause: "Developer: Restart Extension Host" resets the fd table without a full VSCode restart; reducing transcript retention (
cleanupPeriodDays) slows the leak proportionally.