Skip to content

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

Closed

Description

@laffo16

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.
  4. Resume an existing Codex session.
  5. Observe that Codex may display:
    • SessionStart hook (failed)
    • error: hook timed out after 60s
  6. 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.

Activity

  1. added
    bugSomething isn't working
    CLIIssues related to the Codex CLI
    windows-osIssues related to Codex on Windows systems
    hooksIssues related to event hooks
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    on May 3, 2026
  2. removed
    windows-osIssues related to Codex on Windows systems
    CLIIssues related to the Codex CLI
    sessionIssues involving session (thread) management, resuming, forking, naming, archiving
    on May 3, 2026
  3. laffo16 commented on May 4, 2026

    @laffo16
    Author

    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
    Image

    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.

  4. oxysoft commented on May 8, 2026

    @oxysoft

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinghooksIssues related to event hooks

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions