Summary
plugins/claude-ops/hooks/session-event-log.test.sh failed once on CI with no change to the hook or the test:
plugins/claude-ops/hooks/session-event-log.test.sh: line 53: printf: write error: Broken pipe
FAIL: 33 parallel fires → 33 lines: expected '33', got '32'
FAIL: 33 parallel fires → every line parses: expected '33', got '32'
Observed on test-linux (3) in run 34627984970 attempt 1 (PR #4092, head a6c2ff3). The hooks directory on that head is byte-identical to main. The same shard passed on the previous head of the same PR, the re-run (attempt 2) passed, and the suite passes 5 of 5 consecutive local runs (68 assertions each).
Where
The case fires the hook 33 times in parallel through the test's run helper, which pipes the payload with printf '%s' "$body" | ... bash "$HOOK", then asserts 33 intact JSONL lines. Line 53 is that printf.
Reading
The Broken pipe means one hook process closed its stdin before the writer finished. The hook reads stdin under read -t with a default idle of 2 s (CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT), so under 33-way fan-out on a loaded runner one process most likely timed out (or otherwise returned) before its payload arrived, wrote nothing for that event, and the writer's printf then failed on the closed pipe. That is a scheduling race in the test's load shape, not a lost line in the append path (the 32 lines that did land parsed cleanly).
Suggested fix
One of, in order of preference:
- Make the test robust to runner load: raise the stdin read timeout for this case only (the env block already supports it), or have the
run helper hand the payload over a here-string or temp file so the write completes before the hook starts reading.
- Keep the fan-out but assert on the count of writers whose
printf succeeded, and separately assert that every landed line parses, so a slow runner reports "N of 33 delivered" instead of a false append failure.
Not a fix: lowering the assertion to 32, or retrying the case.
Evidence
Summary
plugins/claude-ops/hooks/session-event-log.test.shfailed once on CI with no change to the hook or the test:Observed on
test-linux (3)in run 34627984970 attempt 1 (PR #4092, head a6c2ff3). The hooks directory on that head is byte-identical tomain. The same shard passed on the previous head of the same PR, the re-run (attempt 2) passed, and the suite passes 5 of 5 consecutive local runs (68 assertions each).Where
The case fires the hook 33 times in parallel through the test's
runhelper, which pipes the payload withprintf '%s' "$body" | ... bash "$HOOK", then asserts 33 intact JSONL lines. Line 53 is thatprintf.Reading
The
Broken pipemeans one hook process closed its stdin before the writer finished. The hook reads stdin underread -twith a default idle of 2 s (CLAUDE_PLUGIN_OPTION_STDIN_READ_TIMEOUT), so under 33-way fan-out on a loaded runner one process most likely timed out (or otherwise returned) before its payload arrived, wrote nothing for that event, and the writer'sprintfthen failed on the closed pipe. That is a scheduling race in the test's load shape, not a lost line in the append path (the 32 lines that did land parsed cleanly).Suggested fix
One of, in order of preference:
runhelper hand the payload over a here-string or temp file so the write completes before the hook starts reading.printfsucceeded, and separately assert that every landed line parses, so a slow runner reports "N of 33 delivered" instead of a false append failure.Not a fix: lowering the assertion to 32, or retrying the case.
Evidence