Linux: hook blocked reading stdin (pipe kept open, no EOF) wedges the session for hours; declared hook timeout not enforced (2.1.246)
Environment
- Claude Code 2.1.246 (version of the wedged process, taken from the session transcript; client updated to 2.1.251 during triage — CHANGELOG through 2.1.250 lists no related fix)
- Linux x86_64 (Debian-based), zsh login shell
- Hooks declared in
~/.claude/settings.json, each entry with an explicittimeoutfield (5–15s)
What happened (twice in one day, same session)
- A turn ended while two Bash
run_in_backgroundtasks were still running. - Their completion notifications were never delivered. The session froze, showing the
statusMessageof one of our hooks in the spinner. The background tasks themselves kept running and finished normally. - First freeze lasted ~8 hours and ended only when the user interrupted; the second (same day, ~1.5h) ended with a process restart. After the interrupt/restart, the pending task notifications arrived immediately.
Measured mechanism (hook side)
The hook script read its stdin payload with an unbounded read (dd bs=1048577 count=1; a sibling hook used payload=$(cat)). Measured with process substitution so only the script is timed:
$ time sh hook.sh < <(sleep 6) # pipe open, no EOF
6.003s # blocks exactly as long as the pipe stays open
$ echo '{"hook_event_name":"PreToolUse"}' | time sh hook.sh
0.052s # normal delivery with EOF is instant
So during the freeze the client kept the hook's stdin pipe open without EOF for hours — scaled from the 6s probe, this fully explains the 8h wedge.
Declared timeout not enforced
The wedged hook entry declared "timeout": 5. The hook process was not terminated (8h > 5s). This matches #87289 (timeout not applied while hook blocks reading stdin) and #85250 (timeout not enforced parent-side); the pipe-kept-open behavior matches #78756. All three are currently Windows-labeled or unlabeled — this is a Linux data point with a hard measurement.
Mitigation that works (and the ask)
Wrapping the stdin read inside the script (timeout 2 dd …) ends the hang class: verified 2.043s with a 30s-open pipe, normal path unchanged.
Ask: (1) close the hook's stdin after writing the payload on every code path, including the end-of-turn/notification-delivery path; (2) enforce the declared per-hook timeout parent-side even while the hook blocks on stdin.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗