SessionStart hook on resume can report `hook timed out after 60s` even when the hook exits 0 in ~4s

Resolved 💬 2 comments Opened May 3, 2026 by laffo16 Closed May 4, 2026

On Windows, Codex CLI resumed a session and showed:

  • SessionStart hook (failed)
  • error: hook timed out after 60s

For that same resumed session:

  • the outer SessionStart wrapper finished exit=0 in 4085ms
  • the inner hook finished outcome=success in 3218ms
  • the session was still running immediately afterward

This looks like a false or misattributed SessionStart timeout banner on the
resume path rather than a real 60s timeout of the successful hook invocation.

Environment

  • Codex CLI: 0.128.0
  • OS: Windows 10
  • Shell: PowerShell 7
  • Terminal emulator: Windows Terminal
  • [features].codex_hooks = true
  • [features].tui_app_server = true
  • Hook type: SessionStart command hook
  • Configured SessionStart timeout: 60

Hook Setup

The specimen was taken with a SessionStart command hook configured at
timeout: 60.

The hook ran through a PowerShell wrapper that:

  • logged start/end timing
  • synchronously invoked the real hook work
  • exited with the real hook result

Sanitized shape:

config.toml

[features]
codex_hooks = true
tui_app_server = true

hooks.json

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "& \"<sessionstart-wrapper>.ps1\"",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

Observed

On resume, Codex displayed the failure banner above.

For the same resumed session id, local timing showed:

phase=start ... sessionId=<resumed-session-id> source=resume
phase=end ... sessionId=<resumed-session-id> source=resume exit=0 elapsedMs=4085

For that same resumed session id, the inner hook log showed:

event=SessionStart outcome=success ... sessionId=<resumed-session-id> ... totalMs=3218

Immediately afterward, a direct status check for that same resumed session id
returned:

{
  "found": true,
  "sessionId": "<resumed-session-id>",
  "state": "running"
}

Minimal Repro Shape

I do not yet have a tiny standalone repro outside my normal setup, but the
observed shape is:

  1. Enable a SessionStart command hook on Windows.
  2. Set timeout to 60.
  3. Run the hook through a wrapper that logs timing and then invokes the real

hook synchronously.

  1. Resume an existing Codex session.
  2. Observe that Codex may display:
  • SessionStart hook (failed)
  • error: hook timed out after 60s
  1. Compare the banner against hook-side timing for the same resumed session id.

In this specimen, hook-side timing contradicted the UI banner.

Expected

  • If the hook really times out, the same resumed-session invocation should not

later show successful completion.

  • If the hook completes successfully well inside the timeout window, Codex

should not show a timeout failure banner for that resumed session.

Evidence

1. User-visible failure

The terminal UI displayed:

SessionStart hook (failed)
error: hook timed out after 60s

2. Outer wrapper completed successfully for the resumed session

phase=start ... sessionId=<resumed-session-id> source=resume
phase=end ... sessionId=<resumed-session-id> source=resume exit=0 elapsedMs=4085

3. Inner hook completed successfully for the same resumed session

event=SessionStart outcome=success ... sessionId=<resumed-session-id> ... totalMs=3218

4. The resumed session was still running afterward

{
  "found": true,
  "sessionId": "<resumed-session-id>",
  "state": "running"
}

Possible Clue

Secondary clue only, not required for the main claim:

  • A separate startup-path wrapper invocation on a different internal session id

exited quickly with code 1.

  • That may explain the wrong banner attribution if Codex is mixing startup-path

and resume-path hook outcomes internally.

Suspected Issue

Codex may be surfacing stale SessionStart failure state on resume, or
attributing a different startup-path hook outcome to the resumed session UI.

View original on GitHub ↗

2 Comments

laffo16 · 3 months ago

Final validation against the normal artifacts\cli\current build also passed cleanly on the same first-post-boot repro path.

---

Apologies for the noise and any inconvenience caused while we investigated this. After digging further, this appears to have been a local issue in our codex-state helper chain rather than a Codex bug.

We were able to identify the root cause and reproduce/fix it locally on Windows.

Following up on the original report: based on the first evidence we had, this initially looked like a Codex-side false timeout or wrong-attribution problem on the resume path. After adding deeper instrumentation and expanding the repro set, that conclusion no longer appears correct.

Investigation summary

Our investigation ended up happening in two stages:

  1. We first investigated Codex itself.
  2. Once the deeper Codex hook-runner logs were in place, we then traced the issue into our local hook wrapper / broker startup path.

More concretely, we:

  • reproduced the failure on real cold first-post-boot startups
  • compared those later specimens against the original resume-path report that motivated this issue
  • built a patched local Codex binary so we could distinguish:
  • waiting for the hook child to exit
  • vs waiting for stdout / stderr EOF after the child had already exited
  • confirmed that Codex was not stuck because the hook process was still running
  • confirmed instead that the top-level hook child had already exited 0, and

Codex was timing out later in the drain phase

  • then instrumented our local codex-state wrapper chain and broker startup
  • tested multiple wrapper-side fixes until the cold-start repro disappeared
  • finally promoted the fix into the normal published artifacts\cli\current build and reran the original cold reboot repro successfully

Root cause

The issue was not really a SessionStart-specific problem.

SessionStart was usually just the first event in the chain after reboot, so it was the first code path that touched our local codex-state broker and exposed the bug.

On cold startup, our codex-state broker auto-start path on Windows launched the long-lived broker with UseShellExecute = false. That allowed transient hook pipe handles from the triggering Codex hook process tree to leak into the broker process tree.

In simple terms:

  • Codex was not really hanging in its main logic
  • it was waiting for the hook process to fully finish and for its output pipes to close
  • our wrapper started a background broker process on that first cold run
  • that background process inherited the same hook-related handles
  • so even after the visible hook process exited successfully, Windows still kept those pipes open
  • Codex then waited for EOF that never arrived and eventually reported a hook timeout

What Codex then saw was:

  • the top-level hook child exited 0 in about 4s to 5s
  • but stdout / stderr never reached EOF
  • so Codex kept waiting in the drain phase and eventually hit the 60s timeout

That is why it looked like a SessionStart freeze even though the actual hook work had already succeeded.

What we tested

We used a locally patched Codex build to add more detailed hook-runner logging so we could distinguish:

  • waiting for the child process to exit
  • vs waiting for stdout / stderr drain after the child had already exited

The important failing cold-start specimens showed:

  • child_exited first
  • then stream_drain_timeout
  • timeout classification suspected_inherited_handle
  • 0 bytes ever read on both stdout and stderr

<img width="695" height="185" alt="Image" src="https://github.com/user-attachments/assets/178e2cbb-a65d-4860-9d40-e0f152fc68a9" />

We then tested two local codex-state fixes:

  1. Redirecting the broker child stdio only
  2. Changing the Windows cold broker auto-start path so the broker is launched detached from the transient hook process tree

The stdio-redirection-only change was not sufficient.

The detached Windows broker launch cleared the first-post-boot repro on the same machine. After that change:

  • the broker still started during the same cold SessionStart window
  • the hook still completed successfully
  • both streams reached EOF normally
  • Codex recorded SessionStart hook completed ... status=Completed
  • the timeout banner no longer appeared

We then promoted that fix into the normal published artifacts\cli\current build and reran the same cold reboot test. That final integrated run was also clean, which gave us confidence that the real shared fix path was correct and that we were not only proving the behavior of a one-off experiment build.

Fix

We patched our local codex-state broker auto-start path on Windows so the background broker no longer inherits the transient hook pipe handles from the triggering Codex hook process tree.

No change was needed to SessionStart event semantics themselves, and no change was needed to the broker's actual IPC model; it still communicates over its named pipe as before.

Warning For Hook Wrapper Authors

If you build wrappers around Codex hooks on Windows, be careful about launching long-lived background children from inside the hook process tree.

In particular, avoid:

  • starting a background service/process from the hook path while it can still inherit the hook process handles
  • using launch patterns that let the child keep inherited stdout / stderr or other inheritable handles from the hook chain

That can create a false-positive hook timeout where:

  • the useful hook work already succeeded
  • the top-level hook child already exited 0
  • but Codex still waits for EOF on the inherited pipes and eventually reports a timeout

The safe pattern is:

  • if a hook needs to start a long-lived background service, detach it cleanly from the transient hook process tree
  • keep the real service IPC separate from hook stdio

Important clarification

This was likely not specific to SessionStart in principle.

SessionStart was just the first event most likely to hit the cold broker-start path after reboot. Other first-touch broker events could have exposed the same underlying issue.

TL;DR

  • Root cause: our local codex-state broker startup on Windows, not Codex SessionStart itself
  • Why the 60s timeout happened: the hook child exited, but our broker kept inherited hook pipes open, so Codex waited for EOF and timed out during drain
  • Why it looked like SessionStart: that was usually the first event to touch the broker after boot
  • Why Codex looked hung: it was waiting for pipe close / EOF, not for the hook logic to keep doing work
  • Fix: launch the broker so it is detached from those transient hook pipe handles
  • Result: cold first-post-boot startup now completes cleanly

We’ll close this from our side unless the extra hook-runner diagnostics are still useful upstream. Those diagnostics were helpful in proving that this was a drain_after_exit / EOF problem rather than the hook process still running.

oxysoft · 3 months ago

Indexed this hook ticket in the umbrella tracker: #21753

Goal: collect the scattered Codex hook requests and bugs into one parity matrix for Full Claude Code Hook Parity (29+), while preserving this issue as the detailed thread for its specific behavior.