Skip to content

SessionStart hook is killed by its own 5s timeout, silently dropping CODEX_COMPANION_SESSION_ID #670

Description

@maxfuel

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.

What the kill costs

The SessionStart branch does exactly one thing:

function handleSessionStart(input) {
  appendEnvVar(SESSION_ID_ENV, input.session_id);         // CODEX_COMPANION_SESSION_ID
  appendEnvVar(TRANSCRIPT_PATH_ENV, input.transcript_path);
  appendEnvVar(PLUGIN_DATA_ENV, process.env[PLUGIN_DATA_ENV]);
}

scripts/session-lifecycle-hook.mjs

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:

codex        session-lifecycle-hook.mjs SessionStart   recorded=6    avg=5390ms   max=5889ms   timedOut=6
superpowers  run-hook.cmd session-start                recorded=59   avg=2209ms   max=6839ms   timedOut=0
vercel       session-start-profiler.mjs                recorded=56   avg=1990ms   max=6410ms   timedOut=0
vercel       inject-claude-md.mjs                      recorded=56   avg=1385ms   max=6431ms   timedOut=0
caveman      caveman-activate.js                       recorded=58   avg=1154ms   max=2778ms   timedOut=0

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:

run1: 278ms exit=0
run2: 284ms exit=0
run3: 296ms exit=0

~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

  1. 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.
  2. 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.
  3. (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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions