Expose structured error data for turn/steer no-active-turn and turn-id mismatch failures

Open 💬 0 comments Opened Jul 7, 2026 by greygoody

What variant of Codex are you using?

Codex app-server v2.

What feature would you like to see?

turn/steer should expose stable, machine-readable error data for these two failure conditions:

  • no active turn exists;
  • the supplied expectedTurnId does not match the active turn.

Today, the adjacent ActiveTurnNotSteerable case returns structured TurnError.codexErrorInfo, while NoActiveTurn and ExpectedTurnMismatch return only human-readable JSON-RPC messages.

Observed on main at commit f659eb12bc8cecb976d92db192d9b2983c8053ff.

Current behavior

In codex-rs/app-server/src/request_processors/turn_processor.rs, the turn/steer error mapping currently produces:

  • SteerInputError::NoActiveTurn(_)
  • message: no active turn to steer
  • error.data = None
  • SteerInputError::ExpectedTurnMismatch { expected, actual }
  • message: expected active turn id ... but found ...
  • error.data = None
  • SteerInputError::ActiveTurnNotSteerable { turn_kind }
  • message plus structured TurnError
  • codexErrorInfo = ActiveTurnNotSteerable { turnKind }

The protocol enum in codex-rs/app-server-protocol/src/protocol/v2/shared.rs likewise exposes ActiveTurnNotSteerable, but no equivalent machine-readable variants for the other two steer failures.

Why this matters

External app-server clients need to distinguish:

  • a valid thread with no active turn;
  • a stale or mismatched expected turn id;
  • an active turn that exists but cannot be steered.

Without structured data for the first two cases, clients must either parse English error messages or collapse distinct recovery conditions into one generic failure. That is brittle across versions and makes queue recovery, supervision, and external-client state handling less reliable.

A related real-world example appears in #26413, where no active turn to steer is part of queued follow-up recovery behavior. Broader related discussions include #20943 and #25914, but this proposal is intentionally narrower than lifecycle redesign or active Desktop-session attachment.

Smallest proposed change

Add a stable machine-readable representation for:

  • NoActiveTurn;
  • ExpectedTurnMismatch { expectedTurnId, actualTurnId }.

Preserve the existing:

  • JSON-RPC error code;
  • human-readable messages;
  • successful turn/steer behavior;
  • ActiveTurnNotSteerable behavior.

The exact protocol representation could be either new CodexErrorInfo variants or another steer-specific structured error shape, depending on the intended app-server contract.

Non-goals

  • no active Desktop thread discovery or attachment;
  • no cross-process turn ownership changes;
  • no remote-control or transport changes;
  • no lifecycle redesign;
  • no model-behavior changes.

Suggested test boundary

Two public app-server JSON-RPC integration tests:

  1. steer a valid thread with no active turn and assert structured error data;
  2. steer an active thread with a mismatched expectedTurnId and assert structured expected/actual ids.

Both tests should also assert that the existing human-readable messages remain unchanged.

Compatibility

This should be additive for clients that ignore error.data. Generated JSON/TypeScript/Python protocol artifacts and app-server compatibility checks would need to be updated consistently.

Contribution

Does the Codex team agree that these two turn/steer failures should expose stable machine-readable data? If so, which protocol representation best matches the intended app-server contract?

I can prepare a focused PR with integration coverage if explicitly invited under the current contribution policy.

View original on GitHub ↗