openai/codex · error · TurnError

{message}

Error message

{message}

What it means

TurnError is the structured failure payload carried in Turn.error (app-server v2 protocol, thread_data.rs) whenever a turn ends with status "failed"; the thiserror #[error("{message}")] attribute only defines its Display, so printing the value yields its message field. It exists so thread/read and turn-event clients receive machine-parsable failure data: message for the human-readable reason, optional codexErrorInfo for structured Codex error info, and optional additionalDetails for extra context.

Source

Thrown at codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs:391

}

#[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum TurnItemsView {
    /// `items` was not loaded for this turn. The field is intentionally empty.
    NotLoaded,
    /// `items` contains only a display summary for this turn.
    Summary,
    /// `items` contains every ThreadItem available from persisted app-server history for this turn.
    #[default]
    Full,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, Error)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
#[error("{message}")]
pub struct TurnError {
    pub message: String,
    pub codex_error_info: Option<CodexErrorInfo>,
    #[serde(default)]
    pub additional_details: Option<String>,
}

View on GitHub (pinned to 339751715c)

Solutions

  1. Read turn.error.message for the human-readable failure reason of the failed turn
  2. Inspect turn.error.codexErrorInfo for a machine-readable code (for example usage-limit errors) before falling back to generic failure handling
  3. Check turn.error.additionalDetails for supplementary context some failures attach
  4. Start a new turn for transient causes (network drop, provider 5xx); failed turns are terminal and app-server does not retry them

Example fix

// before
const turn = await rpc('thread/read', { threadId });
console.log(turn.error); // opaque object, fields unknown

// after
if (turn.status === 'failed' && turn.error) {
  const { message, codexErrorInfo, additionalDetails } = turn.error;
  reportFailure(message, codexErrorInfo ?? undefined, additionalDetails ?? undefined);
}
Defensive patterns

Strategy: type-guard

Type guard

function isFailedTurn(turn: Turn): turn is Turn & { status: 'failed'; error: TurnError } {
  return turn.status === 'failed' && turn.error != null;
}

Try / catch

Nothing to catch: TurnError arrives as data on Turn.error, never as a thrown exception. Narrow on turn.status === 'failed' with a type guard, then destructure message / codexErrorInfo / additionalDetails.

Prevention

When it happens

Trigger: A turn fails mid-flight (model/provider request error, interrupted SSE stream, usage limit hit, invalid model or provider config) and app-server transitions Turn.status to failed, attaching TurnError. Encountered when reading a thread with a persisted failed turn via thread/read or when consuming live turn completion events.

Common situations: IDE/editor integrations that render app-server v2 threads and must show why a turn failed; resuming old rollouts where a turn died mid-stream; code that needs to branch on usage-limit failures (codexErrorInfo populated) versus transient network failures.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/6598f64f356a5d2e. Report an issue: GitHub.