[BUG] Stale history.jsonl.lock directory causes indefinite freeze (no PID-based recovery)
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?
Claude Code uses a directory-based lock (~/.claude/history.jsonl.lock) to synchronize writes to history.jsonl. However, if this lock directory exists from a previous crashed/killed session or from copying/restoring a .claude backup, Claude Code freezes indefinitely on startup.
The lock mechanism has no stale lock detection — it doesn't check if the process that created the lock is still alive.
Root Cause
The locking mechanism:
- Acquire:
mkdir history.jsonl.lock(atomic, fails if exists) - Write: append to
history.jsonl - Release:
rmdir history.jsonl.lock
If step 3 never runs (crash, kill -9, Ctrl+C during startup, or copying .claude directory), the lock directory remains. On next startup, Claude sees the directory exists, assumes another process holds the lock, and waits forever.
Reproduction Steps
Method 1: Backup/restore
- Copy
~/.claudeto~/.claude_backupwhile Claude is running (or after a crash) - Restore:
cp -R ~/.claude_backup/* ~/.claude/ - Start Claude Code
- Result: Freeze on first query (before any output)
Method 2: Simulate crash
- Start Claude Code
- Immediately
kill -9 <pid>during first prompt submission - Restart Claude Code
- Result: Freeze (lock directory remains from killed process)
What Should Happen?
Claude Code should detect stale locks and recover automatically:
- Store PID in lock: Write the owning process PID to a file inside the lock directory
- Check PID on conflict: If lock exists, check if the PID is still alive (
kill -0 <pid>) - Auto-cleanup: If PID is dead, remove the stale lock and proceed
- Timeout: After N seconds of waiting, warn user and offer to force-acquire
Error Messages/Logs
No error message — silent freeze. The process appears to hang waiting for lock acquisition.
Workaround
# Remove stale lock manually
rmdir ~/.claude/history.jsonl.lock
# Or add to shell profile as safety alias
alias claude='rmdir ~/.claude/history.jsonl.lock 2>/dev/null; command claude'
Environment
- OS: macOS (Darwin 25.2.0), but likely affects all platforms
- Claude Code Version: Latest
- Shell: zsh
Related Issues
- #15739 - Windows race condition with
history.jsonl.lock(different bug, same file) - #22057 - Mentions stale lock as contributing factor to CPU loop
Additional Context
The history.jsonl.lock in my backup was an empty directory — the lock mechanism uses directory existence as the lock signal, with no contents to identify the owning process. This makes stale lock detection impossible without external tracking.
This issue has 7 comments on GitHub. Read the full discussion on GitHub ↗