Skip to content

fix(codex-cli): report spawn timeouts as timeouts, not exit code -1 - #719

Open
annieyii wants to merge 1 commit into
nashsu:mainfrom
annieyii:fix/codex-cli-timeout-diagnostic
Open

annieyii wants to merge 1 commit into
nashsu:mainfrom
annieyii:fix/codex-cli-timeout-diagnostic

Conversation

@annieyii

@annieyii annieyii commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Closes #718.

When the Codex CLI spawn timeout fires, users saw Codex CLI exited with code -1: followed by the process's stderr. -1 is a sentinel codex_cli.rs
invents, not something Codex returns, so the message named a crash that never
happened. The sentence that did name the cause was appended to the tail of
stderr, behind up to STDERR_LIMIT_BYTES of unrelated startup output, so it was
effectively invisible.

Changes

src-tauri/src/commands/codex_cli.rs

  • done_payload() emits timedOut and timeoutMinutes as their own fields.
    code now carries whatever the child reported instead of being overwritten
    with -1.
  • The timeout sentence is no longer appended to stderr, since the fact is
    structured now. A consequence worth naming: [stderr truncated] was
    suppressed on timeouts by the else if that carried that append, and now
    applies to them too.

src/lib/codex-cli-transport.ts

  • timedOut is checked ahead of both existing branches. This ordering is
    load-bearing rather than cosmetic: the timeout task removes the child handle
    in order to kill it, so code is null here on every platform. The
    exit-code branch skips null outright, and the branch after it calls
    onDone once any agent message has arrived, so a timeout that had already
    produced partial output would be reported to the user as a success.
  • The message leads with the cause and keeps the captured diagnostic below it,
    under a heading naming the stream it actually came from. It states only what
    was observed, that LLM Wiki did not see the run report completion and stopped
    waiting, rather than that the CLI crashed or that the kill succeeded: the
    child can exit on its own just before the deadline and still be marked timed
    out.

Scope and limits

  • code is None on the timeout path because the handle was taken away, not
    because a status was observed. start_kill()'s result is still unchecked;
    this change does not try to fix that, it stops the message from claiming
    otherwise.
  • The Rust test pins the emitted key names. The TypeScript side reads them by
    hand and can still drift independently; nothing here exercises a real Tauri
    emit end to end.
  • A cancel and a timeout can still race at the deadline. Behavior is unchanged
    from before this patch, where the same race produced code -1.
  • Codex CLI detection is untouched. fix(codex-cli): tolerate slow cold-start version detection #711 adjusts the codex --version timeout
    and applies cleanly over this change; the two do not overlap.

Tests

src/lib/codex-cli-transport.test.ts, 17 passing. Three added:

  • A timeout leads with the cause and keeps unrelated stderr below it.
  • A timeout that already produced a partial agent message errors rather than
    calling onDone. This is the regression the ordering exists to prevent; it
    fails if the timedOut check moves back below the others.
  • Fallback output is labelled stdout when stderr is empty.

src-tauri/src/commands/codex_cli.rs, 9 passing, 2 added over done_payload().
Mutating timedOut to timed_out turns both red; before this test that typo
would have shipped with the whole suite green.

Both the ordering assertion and the key-name assertion were mutation-tested by
reverting the behaviour they guard and confirming a named test goes red.

Verification

npm run build, npm run test:mocks (132 files / 1879 tests), npm run mcp:test (22/22), cargo build, cargo test, cargo fmt --check,
git diff --check.

When the Codex CLI spawn timeout fires, the transport reported "Codex CLI
exited with code -1" followed by the process's stderr. Neither half named
the cause. -1 is a sentinel this file invents and Codex never returns, so
the message described a crash that did not happen. The line that did name
the cause was appended to the tail of stderr, which by then may hold a
full STDERR_LIMIT_BYTES of unrelated output, so what the user reads is
whatever Codex printed at startup rather than the timeout.

The timeout now travels in its own field. done_payload() emits timedOut
and timeoutMinutes alongside code, and code carries whatever the child
reported instead of being overwritten.

code is None on that path because the timeout task removes the child
handle in order to kill it, not because a status was observed, so
timedOut is the only dependable signal that a run was stopped. The
frontend checks it ahead of both existing branches: the exit-code branch
skips null outright, and the branch after it calls onDone once any agent
message has arrived, so a timeout that had already produced partial
output would otherwise have been reported as a success.

The message states only what was observed -- that LLM Wiki did not see
the run report completion and stopped waiting. It does not say the CLI
crashed or that the kill succeeded, because the child can exit on its own
just before the deadline and still be marked timed out. The captured
diagnostic is kept below the cause, under a heading naming the stream it
came from.

Two smaller corrections in the same block: the [stderr truncated] marker
was suppressed on timeouts by an else-if and now applies to them too, and
the done payload's key names are pinned by a Rust test rather than by
reading the two files side by side. That test covers the Rust half only;
the frontend can still drift independently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codex CLI spawn timeout is reported as exit code -1, with the cause buried at the end of stderr

1 participant