/resume picker enters sustained high-CPU spin loop (~140%) with alternating selection — binary analysis of 2.1.218
Summary
The /resume session picker enters a sustained spin loop — 140–153% CPU, ~800 MB V8 heap — while the highlighted selection alternates between two adjacent rows at ~300 ms intervals. Reproduces on Windows 11 Enterprise 10.0.26200.
Environment
- Version: 2.1.218
- OS: Windows 11 Enterprise 10.0.26200 (Claude Code in PowerShell)
Steps to reproduce
- Type
/resumeinside an active Claude Code session. - Leave the picker open with ≥1 session in the list.
- Observe process CPU — does not drop to ~0% after the list renders.
Root cause (binary analysis of claude.exe)
Three defects interact to produce the loop:
Bug 1 — Date.now() as React key (offsets ~254,384,200 and ~252,666,300):
return ZW.jsx(jgS, {onDone: e, onResume: n}, Date.now());
A changed key forces full unmount → remount. Currently the handler is called once per invocation so the key is stable — but any re-invocation triggers an immediate remount.
Bug 2 — useEffect([y]) where y = useCallback(..., [onDone]) (jgS / NsS, offsets ~254,382,709 and ~252,663,383):
let y = cce.useCallback(async (b, T) => {
let C = b ? await J8s() : await tbr(T);
n(C); // setSessions
}, [e]); // e = onDone
cce.useEffect(() => {
async function b() { let T = await Bxe(gn()); y(false, T); }
b();
}, [y]); // fires whenever onDone reference changes
If onDone gets a new reference (e.g. on remount from Bug 1), y changes, the effect fires, sessions reload from disk, state updates, re-render — loop.
mjo bidirectional focus circuit (offset ~250,774,988):
K4.useEffect(() => { if (A !== void 0) s.current?.(A) }, [A]); // internal → onFocus
K4.useEffect(() => { if (n !== void 0) i({type:"set-focus", value:n}) }, [n]); // prop → reducer
A session reload produces a new array reference → mjo resets focus state → onFocus fires → parent updates focusedValue → prop bounces back. The set-focus guard terminates single-shot loops but not loops driven by repeated array reloads.
The ~300 ms interval is the round-trip latency of the async disk scan in Bxe(gn()) plus React reconciler commit time — not a timer. Ink's drain timer is Gj >> 2 = 16 >> 2 = 4 ms (confirmed at offset 243,187,215) and is unrelated.
Fix
// Bug 1: drop the Date.now() key
return ZW.jsx(jgS, {onDone: e, onResume: n});
// Bug 2: gate the reload to fire once
cce.useEffect(() => {
if (!hasFetched.current) {
hasFetched.current = true;
async function b() { let T = await Bxe(gn()); y(false, T); }
b();
}
}, [y]);
Note: vQS = ResumeConversation (the claude --resume startup picker) does not have these bugs — it uses useEffect([worktreePaths]) and no Date.now() key. The bugs are specific to the in-session /resume slash-command path.