Skip to content

UserPromptSubmit hook still hangs to full timeout on Windows — #443 fix's setTimeout is unref'd #790

Description

@jbeck1563

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions