[BUG] 2-4 second delay on exit (Ctrl-C / /exit) due to sequential FSEvents watcher cleanup
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Summary
Claude Code CLI takes 2-4 seconds to exit after Ctrl-C or /exit command. Profiling reveals the delay is caused by sequential closure of FSEvents file watchers, with each watcher blocking on a semaphore.
Environment
- Claude Code version: 2.1.12 (npm-local install)
- Platform: macOS 15.6.1 (Darwin 24.6.0)
- Architecture: Apple Silicon (ARM64)
- Node.js: v23.x
- Terminal: iTerm2 / Terminal.app (same behavior in both)
Symptoms
- Every exit (Ctrl-C or
/exit) takes 2-4 seconds of hang time before returning to shell - No visible output during the delay
- Delay occurs even with:
- No MCP servers configured
- Empty
~/.claude/todos/and~/.claude/debug/directories - Minimal session activity
- Increased heap size (
--max-old-space-size=16384) - Network disconnected
Diagnostic Process
Ruled out the following potential causes:
- MCP server shutdown (disabled all MCP servers)
- Large config file / stale entries (cleaned up
.claude.json) - GC pressure (increased heap size - no change)
- iTerm2 shell integration (same in vanilla Terminal.app)
- Network teardown (same behavior offline)
- Large history.jsonl (moved file - no change)
- Accumulated todos/debug files (1720 + 1014 files moved - no change)
Root Cause
Used macOS sample command to capture stack traces during exit:
sample <pid> 10 -f /tmp/claude-exit-sample.txt
93% of samples (2575/2770) were blocked in FSEvents cleanup:
2575 Builtins_ArrayForEach
2575 Builtins_CallApiCallbackGeneric
2575 node::HandleWrap::Close
2575 uv_close
2575 uv_fs_event_stop
2575 uv__fsevents_close
2575 uv_sem_wait
2575 semaphore_wait_trap <-- BLOCKED HERE
The issue is that Claude Code:
- Creates many FSEvents watchers (for config files, skill directories, settings, etc.)
- On exit, iterates through watchers with
Array.forEach - Closes each watcher sequentially
- Each
uv__fsevents_closecall blocks waiting on a semaphore
This is a known limitation of libuv's FSEvents implementation on macOS - closing watchers involves thread synchronization that can be slow.
Suggested Fix
Options to consider:
- Close watchers in parallel rather than sequentially with forEach
- Reduce number of watchers - consolidate watched directories where possible
- Use a timeout - force exit after brief cleanup attempt rather than waiting for all watchers
- Lazy watcher cleanup - let the OS clean up on process termination for non-critical watchers
Sample Output (excerpt)
Full sample file available on request. Key section showing the hot path:
Call graph:
2770 Thread_327367281 DispatchQueue_1: com.apple.main-thread (serial)
+ 2770 start (in dyld) + 6076
+ 2770 node::Start(int, char**)
+ 2770 node::NodeMainInstance::Run()
+ 2770 node::SpinEventLoopInternal(node::Environment*)
+ 2765 uv_run
+ 2589 uv__io_poll
+ 2588 uv__stream_io
+ [... callback chain ...]
+ 2575 Builtins_ArrayForEach
+ 2575 node::HandleWrap::Close
+ 2575 uv_close
+ 2575 uv_fs_event_stop
+ 2575 uv__fsevents_close
+ 2575 uv_sem_wait
+ 2575 semaphore_wait_trap
Workaround
None currently known. The delay is consistent regardless of configuration changes.
What Should Happen?
Ideally, hitting Ctrl-C should immediately close the application. Currently I have to wait for several seconds to move on to my next task.
Error Messages/Logs
Steps to Reproduce
See above
Claude Model
Opus
Is this a regression?
Yes, this worked in a previous version
Last Working Version
_No response_
Claude Code Version
2.1.12
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
iTerm2
Additional Information
See above for comprehensive report direct from El Claude himself.
This issue has 7 comments on GitHub. Read the full discussion on GitHub ↗