[BUG] Session bound to an empty/unregistered worktree pins a CPU core at 100% - no-backoff .claude lookup with an absolute-onto-absolute path join (Windows)

Status Open
Reported on v2.1.219
Maintainer reply None cached
Activity 0 comments · opened Jul 25, 2026

Summary

A Claude Code session whose cwd is bound to an empty, git-unregistered worktree directory enters an unbounded retry loop looking for <cwd>/.claude. It pins one CPU core at 100% indefinitely, issuing roughly 400,000 failed CreateFile calls per second. It never recovers on its own - the process has to be killed. It ran for 90+ minutes on my machine before I tracked it down.

Environment

  • Windows 11, AMD Ryzen 9 9950X3D (16C/32T)
  • Claude Code 2.1.219, headless session spawned by the Claude desktop app (1.24012.9)
  • Session flags: --output-format stream-json --input-format stream-json --permission-mode bypassPermissions --include-partial-messages

Symptom

One thread - the main event-loop thread, created at process start - sits permanently in State: Running:

| metric | affected session | identical sibling session |
|---|---|---|
| CPU | 1.00 core, continuously | normal, mostly Wait |
| kernel / user time | 1:06:49 / 0:18:07 (79% kernel) | - |
| context switches | 3,345,594 | - |
| I/O "other" ops/sec | 395,382 | 9 |
| I/O reads / writes per sec | 4 / 0 | 4 / 0 |

The sibling had a byte-identical command line, the same MCP servers, and was started 15 minutes apart - so this is not configuration-dependent.

Thread stack

0  0x0000000000000000
1  ntdll.dll!ZwDelayExecution+0x14
2  ntdll.dll!RtlDelayExecution+0x34
3  KERNELBASE.dll!SwitchToThread+0x1b
4  claude.exe!uv_key_delete+0x1d2af1
5  claude.exe!uv_key_delete+0xe6628
6  claude.exe!uv_key_delete+0xe6718
...

SwitchToThread in a tight loop - a spin-wait that never blocks. (uv_key_delete is merely the nearest exported symbol; the binary is a bundled Node SEA without symbols.)

The actual loop (Process Monitor)

Two CreateFile calls alternating, hundreds of thousands of times per second:

...\.claude\worktrees\priceless-swartz-4f20aa\.claude
    -> NAME NOT FOUND

...\worktrees\priceless-swartz-4f20aa\.claude\D:\AI Projects\TLG2\.claude\worktrees\priceless-swartz-4f20aa\.claude
    -> NAME INVALID

Two distinct defects are visible:

  1. Absolute-onto-absolute path join. The second path is the cwd joined onto itself. On Windows path.join() does not reset on a drive letter the way POSIX does on a leading /, so the result contains a colon mid-path and is structurally invalid - it can never resolve, no matter what exists on disk.
  2. No backoff on the retry. The lookup is retried in a tight SwitchToThread loop: no delay, no attempt cap, no error surfaced.

How the session got into this state

~/.claude/sessions/<pid>.json contained:

{"cwd":"D:\\AI Projects\\TLG2\\.claude\\worktrees\\priceless-swartz-4f20aa","name":"priceless-swartz-4f20aa-4f"}

That directory existed but was completely empty, and was registered in neither git worktree list nor .git/worktrees. Every sibling worktree in the same folder contains a full checkout plus a .claude directory. So worktree creation appears to have failed partway through, and the session was bound to the resulting empty husk as its cwd regardless.

Partial confirmation

Creating the missing <cwd>/.claude directory by hand dropped the syscall rate from ~395,000/sec to ~115,000/sec, but did not stop the spin - consistent with the analysis above, since the malformed second path cannot succeed under any filesystem state.

Expected behaviour

  • Don't bind a session to a worktree whose creation didn't complete, or that isn't registered in git - fail loudly at session start instead.
  • Fix the absolute-onto-absolute path join in the settings/config lookup.
  • Bound the retry: backoff, an attempt cap, and a surfaced error rather than an infinite hot loop.

Workaround

Kill the session process. There is no in-session recovery, and the CPU burn continues until it exits.

View original on GitHub ↗