anthropics / anthropics/claude-code
[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)
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
## Summary
A Claude Code session whose `cwd` is bound to an **empty, git-unregistered worktree directory** enters an unbounded retry loop looking for `/.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/.json` contained:
```json
{"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 `/.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.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by reproducing the failure with a session whose cwd is the empty, unregistered worktree described in the report, then inspect the settings/config lookup and session startup path. Compare the two Windows paths in Process Monitor and verify that an invalid worktree is rejected, the absolute path join is handled correctly, and retries no longer spin indefinitely.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, node.js, python
- Domain
- cli, developer-experience, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100