Session Freeze After File Edit - Lock Contention Deadlock
Summary
Claude Code session becomes completely unresponsive after completing a file edit operation. The freeze occurs during the transition from "PostToolUse hooks completed" back to idle state, when a file system change notification arrives simultaneously.
Environment
- Claude Code Version: 2.1.12
- OS: macOS 15.7.3 (Darwin 24.6.0)
- Architecture: ARM64 (Apple Silicon)
- Shell: zsh
- Terminal: iTerm2
Steps to Reproduce
- Start a Claude Code session with multiple MCP servers configured
- Have PostToolUse hooks configured (e.g., formatter hooks)
- Perform multiple file edit operations in sequence
- After the final edit completes and hooks finish processing, the session freezes
The freeze appears to be timing-dependent and may require:
- Long-running session (6+ hours in this case)
- Multiple concurrent MCP servers
- File edits that trigger PostToolUse hooks
Expected Behavior
Session should return to idle state and accept user input after completing edit operations and hooks.
Actual Behavior
Session freezes completely:
- No response to keyboard input
- Cannot cancel with Ctrl+C
- CPU usage drops to 0%
- Only solution is to kill the terminal tab
Last visible output before freeze:
⎿ Running PostToolUse hooks… (1/2 done)
⏺ Done! I've added: [...]
Diagnostic Information
Process State
PID: 31755 | TTY: ttys005 | State: S+ (Sleeping) | CPU: 0.0% | Elapsed: 6h 34m
Thread Analysis (via sample command)
Main Thread - blocked on lock:
872 Thread_1764110 DispatchQueue_1: com.apple.main-thread (serial)
+ 872 _os_unfair_lock_lock_slow (in libsystem_platform.dylib)
+ 872 __ulock_wait2 (in libsystem_kernel.dylib)
File Watcher Thread - ALSO blocked on same lock type:
872 Thread_1764227: File Watcher
+ 872 _os_unfair_lock_lock_slow (in libsystem_platform.dylib)
+ 872 __ulock_wait2 (in libsystem_kernel.dylib)
All Bun Pool Threads (0-11) - waiting:
872 Thread_1764190: Bun Pool 0
+ 872 __ulock_wait2 (in libsystem_kernel.dylib)
HTTP Client Thread - normal (waiting for events):
872 Thread_1764214: HTTP Client
+ 872 kevent64 (in libsystem_kernel.dylib)
Tokio Runtime Workers - normal (parked):
872 Thread_1968467: tokio-runtime-worker
+ 872 _pthread_cond_wait -> __psynch_cvwait
Root Cause Analysis
This appears to be a lock inversion deadlock between:
- Main Thread: Trying to acquire a lock while transitioning back to input-waiting state after hooks complete
- File Watcher Thread: Trying to acquire the same lock to notify about the file change (the file that was just edited)
The race condition window:
Edit completes → Hooks run → Hooks complete → [RACE WINDOW] → Return to idle
↑
File Watcher detects change and tries to acquire lock
Configuration
PostToolUse Hooks (2 total):
ruff format $CLAUDE_FILE_PATH 2>/dev/null || black $CLAUDE_FILE_PATH 2>/dev/null || true- Hookify plugin PostToolUse hook (10s timeout)
MCP Servers Running:
- Playwright MCP
- Context7 MCP
- Sequential Thinking MCP
- Serena MCP (with language servers)
- GitHub MCP
Child Processes (all healthy)
All MCP servers showed normal kevent wait state - they were not the cause:
PID 32002 (Playwright): S+ state, waiting on kevent
PID 31988 (Context7): S+ state, waiting on kevent
PID 32049 (Seq Think): S+ state, waiting on kevent
PID 32378 (Serena): S+ state, waiting on kevent
PID 32156 (GitHub): S+ state, waiting on kevent
Impact
- Complete session freeze requiring terminal tab closure
- Loss of unsaved conversation context
- MCP server processes orphaned (need manual cleanup)
Workaround
None found. Once frozen:
kill -CONTdoes not help (it's a lock contention, not a signal issue)- Only option is
kill <pid>or closing the terminal
Suggested Investigation Areas
- Lock ordering between main event loop and File Watcher in Bun runtime
- Race condition during state transition from "processing hooks" to "waiting for input"
- File system notification handling during hook completion
- Potential fix: Defer file change processing until main thread is fully idle
Additional Files
Full sample output available upon request.
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗