Skip to content

Windows hook health: OMO SessionStart exceeds 5s and doctor misses incompatible hook output #133

Description

@idnotbe

Summary

On Windows 11 with Codex CLI 0.144.3 and the OMO 4.16.0 marketplace plugin, a normal CLI session showed two unattributed SessionStart failures with hook timed out after 5s, followed by repeated UserPromptSubmit and Stop invalid-JSON failures.

The investigation separated two causes:

  1. OMO owns a confirmed SessionStart timeout risk: three Windows handlers still have a 5-second limit. Isolated probes showed telemetry at 9,128 ms and auto-update at 6,077 ms even with their optional work disabled. The historical CLI output did not identify which handlers produced its two timeout notices.
  2. A co-installed global hook is a confirmed incompatible JSON emitter and is sufficient to reproduce the same Codex classification; current OMO handlers passed the isolated output checks. The original CLI notices were not toggled end to end, so their exact emitter remains unverified. LazyCodex can make this actionable by having doctor identify incompatible co-installed hooks and report their source path, elapsed time, and sanitized output shape.

Environment

  • OS: Windows 11 Pro 10.0.26200, AMD64
  • Windows PowerShell: 5.1.26100.8655
  • Node.js: 22.15.0
  • Codex CLI: 0.144.3, installed through global npm
  • Installed OMO marketplace plugin: 4.16.0
  • Latest LazyCodex source checked: 3d7416bff3e6c80ebf5542b4dd12f5c76298d46d (OMO 4.17.1)
  • Latest upstream Codex source checked: 2e156cbe31212ccfa9173cb2679c1a201a940b18
  • Relevant configuration: OMO enabled through the sisyphuslabs marketplace; global UserPromptSubmit and Stop hook groups also enabled

Repository Decision

  • Target repository: code-yeongyu/lazycodex
  • Why this belongs here: the confirmed timeout is defined by OMO hook manifests and persists in current LazyCodex 4.17.1 source. The second actionable gap is LazyCodex hook-health diagnosis: the generic CLI error does not identify which co-installed handler emitted incompatible JSON.
  • LazyCodex evidence:
  • Upstream Codex evidence:
    • Codex reports the handler's configured limit from command_runner.rs.
    • UserPromptSubmit and Stop outputs use strict schemas with unknown fields denied in schema.rs.
    • Empty stdout is accepted as a no-op, while JSON-looking incompatible output is reported as invalid in user_prompt_submit.rs and stop.rs.

Reproduction

  1. On Windows, enable OMO 4.16.0 in Codex and start a normal CLI session. In the reported session, Codex displayed two unattributed SessionStart hook (failed): hook timed out after 5s notices.

  2. Reproduce the OMO timeout risk independently with this sanitized probe. Replace <OMO_PLUGIN_ROOT> with the installed plugin directory:

    $PluginRoot = '<OMO_PLUGIN_ROOT>'
    $Dispatch = Join-Path $PluginRoot 'components\bootstrap\scripts\node-dispatch.ps1'
    $env:CODEX_HOME = Join-Path $env:USERPROFILE '.codex'
    $env:PLUGIN_ROOT = $PluginRoot
    $env:PLUGIN_DATA = Join-Path $env:CODEX_HOME 'plugins\data\sisyphuslabs\omo'
    $env:OMO_CODEX_DISABLE_POSTHOG = '1'
    $env:LAZYCODEX_AUTO_UPDATE_DISABLED = '1'
    $env:CODEX_CODEGRAPH_ENABLED = '0'
    
    $Payload = '{"session_id":"probe-session","transcript_path":null,"cwd":"C:\\temp","hook_event_name":"SessionStart","model":"gpt-5.5","permission_mode":"never","source":"startup"}'
    $Targets = @(
      @{ Name = 'telemetry'; File = 'components\telemetry\dist\cli.js' },
      @{ Name = 'auto-update'; File = 'scripts\auto-update.mjs' },
      @{ Name = 'codegraph'; File = 'components\codegraph\dist\cli.js' }
    )
    
    foreach ($Target in $Targets) {
      1..5 | ForEach-Object {
        $Elapsed = Measure-Command {
          $Payload | powershell.exe -NoProfile -ExecutionPolicy Bypass -File $Dispatch (Join-Path $PluginRoot $Target.File) hook session-start
        }
        [pscustomobject]@{ Handler = $Target.Name; Run = $_; ElapsedMs = [math]::Round($Elapsed.TotalMilliseconds) }
      }
    }

    These controls are present in current source: telemetry opt-out, auto-update disable, and the Codex CodeGraph disable case. Elapsed time includes the nested powershell.exe launcher because that is the real commandWindows path Codex executes.

  3. Treat the first invocation in a fresh parent shell as the cold sample and immediate repeats as warm samples. The timeout risk is reproduced when any handler takes more than its 5,000 ms manifest limit.

  4. Reproduce the separate attribution gap in a disposable project. This creates a hook that drains stdin, exits 0, and writes only incompatible application status JSON:

    $ProbeRoot = Join-Path $env:TEMP 'codex-invalid-hook-output-probe'
    $DotCodex = Join-Path $ProbeRoot '.codex'
    $HookScript = Join-Path $ProbeRoot 'invalid-hook.mjs'
    New-Item -ItemType Directory -Force -Path $DotCodex | Out-Null
    
    @'
    process.stdin.resume();
    process.stdin.on("end", () => {
      process.stdout.write(`${JSON.stringify({ ok: true, skipped: "clean_event" })}\n`);
    });
    '@ | Set-Content -Encoding ascii -Path $HookScript
    
    $Command = 'node "' + $HookScript + '"'
    $Handler = @{ type = 'command'; command = $Command; commandWindows = $Command; timeout = 5 }
    $Hooks = @{
      hooks = @{
        UserPromptSubmit = @(@{ hooks = @($Handler) })
        Stop = @(@{ hooks = @($Handler) })
      }
    }
    $Hooks | ConvertTo-Json -Depth 8 | Set-Content -Encoding ascii -Path (Join-Path $DotCodex 'hooks.json')
  5. Run codex -C $ProbeRoot, approve the two disposable project hooks when prompted, submit hi, and let the turn finish. Codex reports hook returned invalid user prompt submit JSON output and hook returned invalid stop hook JSON output, but the visible failures do not identify the emitting file.

  6. Remove the disposable reproduction after the check:

    Remove-Item -LiteralPath $ProbeRoot -Recurse -Force

Expected Behavior

  • OMO SessionStart handlers should reliably finish within their configured timeout on a cold Windows start, including the PowerShell launcher overhead.
  • LazyCodex diagnostics should identify the exact active hook source that exceeds its timeout or emits incompatible JSON, without exposing the prompt or raw hook payload.
  • A no-op capture/telemetry hook should either emit no stdout or emit a valid event-specific Codex hook output shape.

Actual Behavior

  • The reported CLI session showed two unattributed SessionStart timeouts. Independent OMO probes proved that the telemetry and auto-update handlers can each exceed their configured 5-second limit; they do not prove which handlers emitted the historical notices.
  • A separate global hook was confirmed to emit incompatible status JSON for both events and reproduces the same generic invalid-JSON classification. End-to-end attribution of the original notices remains unverified.
  • The visible error did not name the handler, so OMO and non-OMO hooks initially appeared to be one failure.

Evidence

Safe synthetic Windows probes used the installed OMO 4.16.0 commandWindows launcher while disabling optional external work:

Handler Cold elapsed Configured timeout Result
Telemetry 9,128 ms 5,000 ms Would time out
Auto-update 6,077 ms 5,000 ms Would time out
CodeGraph 4,454 ms 5,000 ms Passed with little headroom

A warm second pass through the same launcher completed in 2,651-2,735 ms, while the first direct telemetry process in a separate pass still took 5,827 ms. The failure is therefore cold-start sensitive.

Synthetic output checks found:

  • OMO rules, ultrawork, ulw-loop, and start-work-continuation no-op paths: exit 0 with empty stdout, accepted by current Codex.
  • OMO triggered output builders: valid event-specific JSON shapes in source and tests.
  • Co-installed global capture hook: exit 0 with ok,event,skipped status JSON for both UserPromptSubmit and Stop; rejected because those fields are outside the Codex hook output schemas.
  • Co-installed final-handoff hook: valid decision,reason output; not the invalid Stop emitter.

No exact open LazyCodex duplicate was found. Closed #57 is the closest JSON-output precedent but addressed a prior CodeGraph SessionStart output defect. Closed #45, #80, and #92 cover missing hook payloads or earlier doctor/layout defects rather than this timeout headroom and handler-attribution case.

Root Cause

Insufficient OMO timeout headroom on Windows is confirmed independently of the historical handler attribution. The manifests allow 5 seconds, but cold PowerShell + Node startup and handler initialization can exceed 5 seconds before optional work matters. Current LazyCodex 4.17.1 retains the same limits. The exact two handlers behind the original CLI notices remain unidentified because the visible output did not include handler names.

An incompatible JSON emitter is also confirmed, but isolated current OMO handlers did not emit it. A co-installed global hook prints internal status JSON to stdout, and that output is sufficient to reproduce Codex's invalid-output classification because its top-level fields are outside the hook schema. The original notices were not verified with a disable/re-enable toggle. The LazyCodex-owned gap is diagnosis and attribution, not the external hook's output implementation.

Proposed Fix

  1. As the minimal immediate fix, raise the telemetry, auto-update, and CodeGraph SessionStart manifest limits from 5 to 15 seconds in their hook fragments, then add deterministic tests that assert the generated marketplace manifests retain those values.
  2. Add an opt-in Windows smoke check that runs each real commandWindows path from a fresh shell and records cold and warm elapsed times. Keep wall-clock thresholds out of the deterministic unit suite.
  3. Extend lazycodex-ai doctor --platform=codex to inventory global plus plugin hooks and run redacted synthetic payload probes. Report handler source, exit code, elapsed time versus configured timeout, stdout byte count, and parsed top-level keys only.
  4. Validate synthetic stdout against the event-specific Codex hook schema. Recommend empty stdout for no-op handlers and explain incompatible keys without printing raw payloads.
  5. Add a regression fixture where a co-installed hook emits {"ok":true} so doctor attributes the invalid output to that file instead of implying OMO emitted it. Moving nonessential startup work behind a detached path can be evaluated separately from the minimal timeout correction.

Verification Plan

  • On a cold Windows test host, run all OMO SessionStart commandWindows handlers repeatedly and confirm no run reaches its configured timeout.
  • Run doctor with a synthetic incompatible global hook and confirm it names that hook, reports only sanitized output metadata, and returns a clear remediation.
  • Confirm OMO no-op and triggered outputs pass current Codex UserPromptSubmit, Stop, and SessionStart schemas.
  • Start Codex with OMO plus the fixed global hook and confirm startup, prompt submission, and stop complete without hook failure notices.

This issue or PR was generated by LazyCodex.
Tag: lazycodex-generated

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions