Windows: stale lock directories block credential persistence after abnormal exit
Description
Claude Code on Windows creates its lockfiles as directories (mkdir-as-mutex pattern) but never removes them on abnormal exit. Once a stale lock directory exists, subsequent /login attempts silently fail to write .credentials.json even though the UI reports "Login successful."
Affected lock paths under %USERPROFILE%\.claude\:
.oauth_refresh.lock.storage-write.lockhistory.jsonl.lock
Steps to reproduce
- Run Claude Code on Windows normally.
- Force-kill the CC process (Task Manager,
taskkill, crash, etc.). - List
%USERPROFILE%\.claude\-- the.lockentries are directories (dmode), not files. - Launch CC again. Session starts as "Not logged in."
- Run
/login. OAuth flow completes, UI displays "Login successful." - Status bar immediately reverts to "Not logged in. Run /login."
.credentials.jsonis never created in.claude\.
Expected behavior
Lock directories should be cleaned up on exit (normal or abnormal), or stale locks should be detected and removed on startup (e.g., via PID check or mtime threshold).
Actual behavior
Orphaned lock directories persist indefinitely. The OAuth flow reports success because the in-memory handshake completes, but the credential-persistence step that depends on acquiring .oauth_refresh.lock silently fails. The loop is indefinite without manual intervention.
Environment
- OS: Windows 11 Pro
- Shell: Git Bash (MINGW64)
- CC versions: reproduced on 2.1.91 and 2.1.150
- Install method: npm global (
%APPDATA%\npm\) - Account: Max subscription, healthy, well under all limits
- Same account on a second Windows machine is unaffected (no orphaned lock dirs)
Diagnostic notes
Ruled out: account block/billing, backend incident, clock skew, Defender/EDR, DPAPI corruption, disk full, OneDrive sync, competing CC binaries, MCP configuration.
Confirmed causal by file watcher: .credentials.json is never written while the lock directories exist. Removing the lock directories restores login. The next /login recreates them as directories, but if CC exits cleanly they are removed -- until the next abnormal exit re-orphans them.
Workaround
Remove the stuck lock directories before launching CC:
find ~/.claude -maxdepth 1 -name "*.lock" -type d -exec rm -rf {} +
The -type d is critical -- only matches the stuck-directory state, not legitimate transient lock files.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗