codex exec --json drops typed codexErrorInfo from terminal errors

Open 💬 1 comment Opened Aug 2, 2026 by mulanfly

Summary

codex exec --json drops the existing typed codexErrorInfo from both top-level error events and nested turn.failed.error events. Downstream automation therefore receives only a human-readable message and cannot reliably distinguish an authentication failure that requires login from transport, runtime, quota, or other failures without parsing prose.

The Core -> App Server path already carries CodexErrorInfo. The information is lost only in the codex-exec JSONL projection.

Current behavior

This is present in both:

  • rust-v0.146.0 (e363b08c9175ac1cbe5893615dd2cb9ddf95043b)
  • current main inspected at 2b5bdcf67547860f2e5c5a605009a70026796b2b

Relevant source path:

  1. codex-rs/protocol/src/error.rs maps a permanent refresh-token failure to CodexErrorInfo::Unauthorized.
  2. App Server v2 TurnError retains codex_error_info: Option<CodexErrorInfo>.
  3. codex-rs/exec/src/exec_events.rs defines ThreadErrorEvent with only message.
  4. codex-rs/exec/src/event_processor_with_jsonl_output.rs projects both ErrorNotification.error and TurnCompleted.turn.error into that message-only type.

As a result, a consumer sees output shaped like:

{"type":"turn.failed","error":{"message":"..."}}

even when App Server supplied codexErrorInfo: "unauthorized".

Expected behavior

Preserve the existing typed classification as an optional additive field:

{"type":"turn.failed","error":{"message":"...","codex_error_info":"unauthorized"}}

Events without typed error information should keep their current serialized shape.

This would let deterministic consumers treat unauthorized as a coarse login-required signal while keeping absent or other classifications on their normal failure paths. It would not require consumers to infer expired/revoked/invalid subreasons from the message; the current unit Unauthorized variant does not expose those subreasons.

Minimal repair outline

  • Add optional codex_error_info to ThreadErrorEvent in codex-rs/exec/src/exec_events.rs, using skip_serializing_if = "Option::is_none" so untyped events remain byte-shape compatible.
  • Preserve the field in both error projections in codex-rs/exec/src/event_processor_with_jsonl_output.rs; default fallback errors to None.
  • Update the public TypeScript event types in sdk/typescript/src/events.ts.
  • Extend codex-rs/exec/tests/event_processor_with_json_output.rs to cover:
  • top-level error;
  • nested turn.failed.error through the saved critical error;
  • direct TurnCompleted.turn.error;
  • serialized JSON containing "codex_error_info":"unauthorized" only when present.

This remains on the existing codex exec --json transport and only exposes information already carried by Core and App Server.

If this approach aligns with the intended public JSONL contract, an invitation to submit the focused patch would be welcome.

View original on GitHub ↗

1 Comment

gdevyatkin-snyk · 20 days ago

Production evidence from codex-cli 0.146.0-alpha.9.2 confirms this affects deterministic recovery, not only auth handling.

In two separate one-page/full-batch calls, the API request was accepted (HTTP 200), native OTEL observed exactly one failed response.completed, JSONL closed as turn.failed, and there were no API/HTTP/timeout/parser/cleanup failures or output tokens. The same frozen audit input later succeeded, while a different phase then reproduced the same terminal shape. Because ThreadErrorEvent retains only message, our content-free telemetry can classify this only as other; it cannot distinguish responseStreamDisconnected, responseStreamConnectionFailed, internalServerError, or another typed variant.

This matters for fail-closed automation: sealed runs must stop, while routine runs may retry only a proven transient. An optional typed field on both top-level error and nested turn.failed.error would let consumers make that decision without retaining or parsing raw messages.

Downstream evidence and acceptance boundary: https://github.com/snyk/a11y-agent/issues/221#issuecomment-5225621234