You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
SessionStart carries the same timeout: 5 as SessionEnd (hooks.json). #474 asks to raise SessionEnd "while leaving the SessionStart timeout unchanged". I have measurements from a Windows machine saying SessionStart needs it too — it has been killed at that ceiling at least 6 times in 9 days.
Killed, those three are never written, and anything downstream resolving the session id or transcript path from the environment starts without them. A cancelled hook is silent at normal verbosity, so there is no signal that it happened.
Measurements
Aggregated the hook_* attachment entries across the 50 most recently modified session transcripts (9 days, 25 project directories), keyed by command:
The max column is the argument. Three of the four sibling SessionStart hooks have peaked above 5 seconds on this machine, in these same sessions. They survive it because they run on the default 60s budget. Under timeout: 5 each of those peaks would have been a kill.
Caveat on the counts: Claude Code does not persist a hook run that succeeds with empty output, so successful runs of this hook are invisible in transcripts. The 6 above are the recorded runs, and all 6 are timeouts — I cannot derive a failure rate from this, only that it has happened at least 6 times.
What it is not
Startup cost, ruled out on the same machine:
bare `node -e "0"` 204ms / 232ms / 200ms
import broker-lifecycle + state + app-server 241ms / 266ms
The hook itself, run directly with stdin closed and CLAUDE_ENV_FILE unset so it performs no writes:
~300ms standalone against a 5000ms budget — the code path is not the problem.
I also considered the readHookInput() → fs.readFileSync(0, "utf8") blocking-stdin cause from #191. I do not think that is what this is.stop-review-gate-hook.mjs still uses the identical pattern, and on this same Windows machine the Stop hook completes fine (recorded=13, avg=1707ms, max=6384ms, timedOut=0). Whatever #191 was, stdin is closing here.
What is left is contention: five SessionStart hooks spawn at the same moment, and the sibling numbers show that moment routinely producing 6+ second outliers. timeout: 5 is simply the tightest budget in the noisiest part of the session, guarding the cheapest work.
Ask
Give SessionStart the same treatment Increase SessionEnd hook timeout to prevent premature cancellation #474 asks for SessionEnd — the default 60s, or 30s to match that request. Three appendFileSync calls do not need a deadline shorter than the observed spawn variance of their own event.
Make the loss visible. Downstream code that reads CODEX_COMPANION_SESSION_ID could report the variable as missing rather than proceeding as though there were no session.
(Optional, unrelated to the 5s)SessionStart needs none of broker-lifecycle.mjs, app-server.mjs, state.mjs, or process.mjs, all imported eagerly at module top level for the SessionEnd path. Moving them behind a dynamic import() inside handleSessionEnd would keep every module-level side effect out of the start path. Measured above, the graph only costs ~250ms, so this is hygiene, not the fix.
Environment
OS
Windows 11 Enterprise LTSC 2024 (10.0.26100)
Claude Code
2.1.240
Plugin
codex 1.0.6 (marketplace openai-codex)
Node
v26.4.0
Related: #474 (same value, SessionEnd), #191 (stdin cause I ruled out here).
SessionStartcarries the sametimeout: 5asSessionEnd(hooks.json). #474 asks to raiseSessionEnd"while leaving theSessionStarttimeout unchanged". I have measurements from a Windows machine sayingSessionStartneeds it too — it has been killed at that ceiling at least 6 times in 9 days.What the kill costs
The
SessionStartbranch does exactly one thing:scripts/session-lifecycle-hook.mjsKilled, those three are never written, and anything downstream resolving the session id or transcript path from the environment starts without them. A cancelled hook is silent at normal verbosity, so there is no signal that it happened.
Measurements
Aggregated the
hook_*attachment entries across the 50 most recently modified session transcripts (9 days, 25 project directories), keyed by command:The max column is the argument. Three of the four sibling
SessionStarthooks have peaked above 5 seconds on this machine, in these same sessions. They survive it because they run on the default 60s budget. Undertimeout: 5each of those peaks would have been a kill.Caveat on the counts: Claude Code does not persist a hook run that succeeds with empty output, so successful runs of this hook are invisible in transcripts. The 6 above are the recorded runs, and all 6 are timeouts — I cannot derive a failure rate from this, only that it has happened at least 6 times.
What it is not
Startup cost, ruled out on the same machine:
The hook itself, run directly with stdin closed and
CLAUDE_ENV_FILEunset so it performs no writes:~300ms standalone against a 5000ms budget — the code path is not the problem.
I also considered the
readHookInput()→fs.readFileSync(0, "utf8")blocking-stdin cause from #191. I do not think that is what this is.stop-review-gate-hook.mjsstill uses the identical pattern, and on this same Windows machine theStophook completes fine (recorded=13, avg=1707ms, max=6384ms, timedOut=0). Whatever #191 was, stdin is closing here.What is left is contention: five
SessionStarthooks spawn at the same moment, and the sibling numbers show that moment routinely producing 6+ second outliers.timeout: 5is simply the tightest budget in the noisiest part of the session, guarding the cheapest work.Ask
SessionStartthe same treatment IncreaseSessionEndhook timeout to prevent premature cancellation #474 asks forSessionEnd— the default 60s, or 30s to match that request. ThreeappendFileSynccalls do not need a deadline shorter than the observed spawn variance of their own event.CODEX_COMPANION_SESSION_IDcould report the variable as missing rather than proceeding as though there were no session.SessionStartneeds none ofbroker-lifecycle.mjs,app-server.mjs,state.mjs, orprocess.mjs, all imported eagerly at module top level for theSessionEndpath. Moving them behind a dynamicimport()insidehandleSessionEndwould keep every module-level side effect out of the start path. Measured above, the graph only costs ~250ms, so this is hygiene, not the fix.Environment
codex1.0.6 (marketplaceopenai-codex)Related: #474 (same value,
SessionEnd), #191 (stdin cause I ruled out here).