Hook timeout does not reclaim a hook blocked in a read syscall — SIGTERM ignored, process survives indefinitely
Summary
A hook that blocks on a read syscall is not reclaimed by its configured timeout. The timeout fires, SIGTERM is delivered, and the process keeps running. Only SIGKILL or SIGQUIT ends it.
Measurement
A Go binary invoked as a hook called io.ReadAll(os.Stdin) unconditionally. When stdin was a FIFO that never reached EOF:
timeout 8 <hook>→ process still alive at 383 secondstimeout 10 <hook>→ process still alive at 200 seconds- In both cases
timeoutitself sat insigsuspend, waiting on a child that would not die - With stdin closed, the same binary returned
rc=0in 49 ms
Goroutine dump of the blocked process (GOTRACEBACK=all):
goroutine 1 gp=0xc000002380 m=4 mp=0xc000075808 [syscall]:
syscall.Syscall(0x0, 0x0, 0xc0000a8000, 0x200)
...
io.ReadAll({0xc1f8c0, 0xc00011e028})
Goroutine 1 is parked in the blocking read on a locked thread, so the Go runtime never reaches its signal handler. Any process whose main thread blocks in an uninterruptible-ish syscall will behave the same way; this is not Go-specific in principle.
Why it matters
The timeout field on a hook is the sanctioned mitigation for a misbehaving hook, and it does not work against the failure mode that most needs it — a hook wedged on I/O rather than spinning. For a UserPromptSubmit hook this stalls every prompt in the session, and a wedged hook prints nothing, so the broken state is indistinguishable on stdout from the healthy "nothing to report" state.
Suggested fix
Escalate on timeout: SIGTERM, wait a short grace period, then SIGKILL. Optionally surface a one-line notice when the escalation is used, so a hook that had to be killed is visible rather than silent.
Environment
- Platform: Linux 6.8.0-136-generic
- Hook types observed:
UserPromptSubmit,PreToolUse - Reproduces with a
commandhook whose stdin is an open FIFO with no writer sending EOF
Related
#78756 reports hooks hanging because the client never closes the hook's stdin pipe (Windows). That is a cause; this issue is about the mitigation — a hook wedged that way cannot be reclaimed by its timeout, whatever wedged it.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗