Summary
The Windows stdin-EOF deadlock from #443 is not fully fixed. The mitigation added — a 1s fallback timer in ponytail-mode-tracker.js — never actually fires when the bug it's guarding against occurs, so the hook still hangs, just to Claude Code's external hook timeout (5000ms per claude-codex-hooks.json) instead of hanging forever.
Root cause
process.stdin.on('data', chunk => { input += chunk; });
process.stdin.on('end', finish);
process.stdin.on('error', () => { finish(); process.exit(0); });
setTimeout(() => { finish(); process.exit(0); }, 1000).unref();
.unref() tells Node "don't let this timer alone keep the process alive." That's backwards for a fallback timer. When the commandWindows PowerShell wrapper swallows the piped prompt JSON (the #443 scenario) and neither 'data' nor 'end' ever fires, the only thing left keeping the event loop alive is process.stdin itself. An unref'd timer competing against a ref'd stdin handle never gets scheduled — so the 1000ms fallback is dead code in exactly the case it exists for. The process just sits until something external kills it.
Evidence
Observed via Claude Code's own hook telemetry (hook_cancelled with timedOut: true) across a 50-session/8-day window on Windows: 2 occurrences, both recorded at exactly 5000ms — Claude Code's configured UserPromptSubmit hook timeout in claude-codex-hooks.json, not the script's intended 1000ms fallback. If the fallback timer were actually firing, these would show ~1000ms, not 5000ms.
So the bug is intermittent (stdin-swallow race, not every prompt) but when it hits, it now costs a full extra ~5s hook stall per prompt submission instead of freezing forever — better than #443, but the advertised "never hang" fallback isn't doing its job.
Fix
Drop .unref() on the fallback timer (or otherwise ensure it can fire independent of stdin's ref state):
setTimeout(() => { finish(); process.exit(0); }, 1000);
A plain (ref'd) timer will fire at 1000ms regardless of whether stdin ever emits, giving the actually-intended ~1s worst case instead of falling through to Claude Code's 5s hook watchdog.
Environment
- OS: Windows 11
- Claude Code: 2.1.258 (native install)
- Ponytail: 4.8.4
Summary
The Windows stdin-EOF deadlock from #443 is not fully fixed. The mitigation added — a 1s fallback timer in
ponytail-mode-tracker.js— never actually fires when the bug it's guarding against occurs, so the hook still hangs, just to Claude Code's external hook timeout (5000ms perclaude-codex-hooks.json) instead of hanging forever.Root cause
.unref()tells Node "don't let this timer alone keep the process alive." That's backwards for a fallback timer. When thecommandWindowsPowerShell wrapper swallows the piped prompt JSON (the #443 scenario) and neither'data'nor'end'ever fires, the only thing left keeping the event loop alive isprocess.stdinitself. An unref'd timer competing against a ref'd stdin handle never gets scheduled — so the 1000ms fallback is dead code in exactly the case it exists for. The process just sits until something external kills it.Evidence
Observed via Claude Code's own hook telemetry (
hook_cancelledwithtimedOut: true) across a 50-session/8-day window on Windows: 2 occurrences, both recorded at exactly 5000ms — Claude Code's configuredUserPromptSubmithook timeout inclaude-codex-hooks.json, not the script's intended 1000ms fallback. If the fallback timer were actually firing, these would show ~1000ms, not 5000ms.So the bug is intermittent (stdin-swallow race, not every prompt) but when it hits, it now costs a full extra ~5s hook stall per prompt submission instead of freezing forever — better than #443, but the advertised "never hang" fallback isn't doing its job.
Fix
Drop
.unref()on the fallback timer (or otherwise ensure it can fire independent of stdin's ref state):A plain (ref'd) timer will fire at 1000ms regardless of whether stdin ever emits, giving the actually-intended ~1s worst case instead of falling through to Claude Code's 5s hook watchdog.
Environment