[app-server] update doc with codex error info (#6941)

Document new codex error info. Also fixed the name from
`codex_error_code` to `codex_error_info`.
This commit is contained in:
Celia Chen
2025-11-20 17:02:37 -08:00
committed by GitHub
Unverified
parent 8e5f38c0f0
commit 7e2165f394
8 changed files with 50 additions and 24 deletions
@@ -50,6 +50,9 @@ macro_rules! v2_enum_from_core {
}
/// This translation layer make sure that we expose codex error code in camel case.
///
/// When an upstream HTTP status is available (for example, from the Responses API or a provider),
/// it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -615,7 +618,7 @@ pub struct Turn {
#[error("{message}")]
pub struct TurnError {
pub message: String,
pub codex_error_code: Option<CodexErrorInfo>,
pub codex_error_info: Option<CodexErrorInfo>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
@@ -1253,7 +1256,7 @@ mod tests {
}
#[test]
fn codex_error_code_serializes_http_status_code_in_camel_case() {
fn codex_error_info_serializes_http_status_code_in_camel_case() {
let value = CodexErrorInfo::ResponseTooManyFailedAttempts {
http_status_code: Some(401),
};
+23
View File
@@ -339,6 +339,29 @@ Event notifications are the server-initiated event stream for thread lifecycles,
The app-server streams JSON-RPC notifications while a turn is running. Each turn starts with `turn/started` (initial `turn`) and ends with `turn/completed` (final `turn` plus token `usage`), and clients subscribe to the events they care about, rendering each item incrementally as updates arrive. The per-item lifecycle is always: `item/started` → zero or more item-specific deltas → `item/completed`.
- `turn/started` — `{ turn }` with the turn id, empty `items`, and `status: "inProgress"`.
- `turn/completed` — `{ turn }` where `turn.status` is `completed`, `interrupted`, or `failed`; failures carry `{ error: { message, codexErrorInfo? } }`.
Today both notifications carry an empty `items` array even when item events were streamed; rely on `item/*` notifications for the canonical item list until this is fixed.
#### Errors
`error` event is emitted whenever the server hits an error mid-turn (for example, upstream model errors or quota limits). Carries the same `{ error: { message, codexErrorInfo? } }` payload as `turn.status: "failed"` and may precede that terminal notification.
`codexErrorInfo` maps to the `CodexErrorInfo` enum. Common values:
- `ContextWindowExceeded`
- `UsageLimitExceeded`
- `HttpConnectionFailed { httpStatusCode? }`: upstream HTTP failures including 4xx/5xx
- `ResponseStreamConnectionFailed { httpStatusCode? }`: failure to connect to the response SSE stream
- `ResponseStreamDisconnected { httpStatusCode? }`: disconnect of the response SSE stream in the middle of a turn before completion
- `ResponseTooManyFailedAttempts { httpStatusCode? }`
- `BadRequest`
- `Unauthorized`
- `SandboxError`
- `InternalServerError`
- `Other`: all unclassified errors
When an upstream HTTP status is available (for example, from the Responses API or a provider), it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.
#### Thread items
`ThreadItem` is the tagged union carried in turn responses and `item/*` notifications. Currently we support events for the following items:
@@ -264,7 +264,7 @@ pub(crate) async fn apply_bespoke_event_handling(
EventMsg::Error(ev) => {
let turn_error = TurnError {
message: ev.message,
codex_error_code: ev.codex_error_code.map(V2CodexErrorInfo::from),
codex_error_info: ev.codex_error_info.map(V2CodexErrorInfo::from),
};
handle_error(conversation_id, turn_error.clone(), &turn_summary_store).await;
outgoing
@@ -278,7 +278,7 @@ pub(crate) async fn apply_bespoke_event_handling(
// but we notify the client.
let turn_error = TurnError {
message: ev.message,
codex_error_code: ev.codex_error_code.map(V2CodexErrorInfo::from),
codex_error_info: ev.codex_error_info.map(V2CodexErrorInfo::from),
};
outgoing
.send_server_notification(ServerNotification::Error(ErrorNotification {
@@ -899,7 +899,7 @@ mod tests {
conversation_id,
TurnError {
message: "boom".to_string(),
codex_error_code: Some(V2CodexErrorInfo::InternalServerError),
codex_error_info: Some(V2CodexErrorInfo::InternalServerError),
},
&turn_summary_store,
)
@@ -910,7 +910,7 @@ mod tests {
turn_summary.last_error,
Some(TurnError {
message: "boom".to_string(),
codex_error_code: Some(V2CodexErrorInfo::InternalServerError),
codex_error_info: Some(V2CodexErrorInfo::InternalServerError),
})
);
Ok(())
@@ -956,7 +956,7 @@ mod tests {
conversation_id,
TurnError {
message: "oops".to_string(),
codex_error_code: None,
codex_error_info: None,
},
&turn_summary_store,
)
@@ -996,7 +996,7 @@ mod tests {
conversation_id,
TurnError {
message: "bad".to_string(),
codex_error_code: Some(V2CodexErrorInfo::Other),
codex_error_info: Some(V2CodexErrorInfo::Other),
},
&turn_summary_store,
)
@@ -1024,7 +1024,7 @@ mod tests {
TurnStatus::Failed {
error: TurnError {
message: "bad".to_string(),
codex_error_code: Some(V2CodexErrorInfo::Other),
codex_error_info: Some(V2CodexErrorInfo::Other),
}
}
);
@@ -1079,7 +1079,7 @@ mod tests {
conversation_a,
TurnError {
message: "a1".to_string(),
codex_error_code: Some(V2CodexErrorInfo::BadRequest),
codex_error_info: Some(V2CodexErrorInfo::BadRequest),
},
&turn_summary_store,
)
@@ -1098,7 +1098,7 @@ mod tests {
conversation_b,
TurnError {
message: "b1".to_string(),
codex_error_code: None,
codex_error_info: None,
},
&turn_summary_store,
)
@@ -1134,7 +1134,7 @@ mod tests {
TurnStatus::Failed {
error: TurnError {
message: "a1".to_string(),
codex_error_code: Some(V2CodexErrorInfo::BadRequest),
codex_error_info: Some(V2CodexErrorInfo::BadRequest),
}
}
);
@@ -1155,7 +1155,7 @@ mod tests {
TurnStatus::Failed {
error: TurnError {
message: "b1".to_string(),
codex_error_code: None,
codex_error_info: None,
}
}
);
+3 -3
View File
@@ -1191,12 +1191,12 @@ impl Session {
message: impl Into<String>,
codex_error: CodexErr,
) {
let codex_error_code = CodexErrorInfo::ResponseStreamDisconnected {
let codex_error_info = CodexErrorInfo::ResponseStreamDisconnected {
http_status_code: codex_error.http_status_code_value(),
};
let event = EventMsg::StreamError(StreamErrorEvent {
message: message.into(),
codex_error_code: Some(codex_error_code),
codex_error_info: Some(codex_error_info),
});
self.send_event(turn_context, event).await;
}
@@ -1689,7 +1689,7 @@ mod handlers {
id: sub_id.clone(),
msg: EventMsg::Error(ErrorEvent {
message: "Failed to shutdown rollout recorder".to_string(),
codex_error_code: Some(CodexErrorInfo::Other),
codex_error_info: Some(CodexErrorInfo::Other),
}),
};
sess.send_event_raw(event).await;
+2 -2
View File
@@ -469,7 +469,7 @@ impl CodexErr {
};
ErrorEvent {
message,
codex_error_code: Some(self.to_codex_protocol_error()),
codex_error_info: Some(self.to_codex_protocol_error()),
}
}
@@ -650,7 +650,7 @@ mod tests {
"prefix: Error while reading the server response: HTTP status client error (429 Too Many Requests) for url (http://example.com/), request id: req-123"
);
assert_eq!(
event.codex_error_code,
event.codex_error_info,
Some(CodexErrorInfo::ResponseStreamConnectionFailed {
http_status_code: Some(429)
})
@@ -540,7 +540,7 @@ fn error_event_produces_error() {
"e1",
EventMsg::Error(codex_core::protocol::ErrorEvent {
message: "boom".to_string(),
codex_error_code: Some(CodexErrorInfo::Other),
codex_error_info: Some(CodexErrorInfo::Other),
}),
));
assert_eq!(
@@ -580,7 +580,7 @@ fn stream_error_event_produces_error() {
"e1",
EventMsg::StreamError(codex_core::protocol::StreamErrorEvent {
message: "retrying".to_string(),
codex_error_code: Some(CodexErrorInfo::Other),
codex_error_info: Some(CodexErrorInfo::Other),
}),
));
assert_eq!(
@@ -599,7 +599,7 @@ fn error_followed_by_task_complete_produces_turn_failed() {
"e1",
EventMsg::Error(ErrorEvent {
message: "boom".to_string(),
codex_error_code: Some(CodexErrorInfo::Other),
codex_error_info: Some(CodexErrorInfo::Other),
}),
);
assert_eq!(
+2 -2
View File
@@ -716,7 +716,7 @@ pub struct ExitedReviewModeEvent {
pub struct ErrorEvent {
pub message: String,
#[serde(default)]
pub codex_error_code: Option<CodexErrorInfo>,
pub codex_error_info: Option<CodexErrorInfo>,
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
@@ -1395,7 +1395,7 @@ pub struct UndoCompletedEvent {
pub struct StreamErrorEvent {
pub message: String,
#[serde(default)]
pub codex_error_code: Option<CodexErrorInfo>,
pub codex_error_info: Option<CodexErrorInfo>,
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, TS)]
+1 -1
View File
@@ -2648,7 +2648,7 @@ fn stream_error_updates_status_indicator() {
id: "sub-1".into(),
msg: EventMsg::StreamError(StreamErrorEvent {
message: msg.to_string(),
codex_error_code: Some(CodexErrorInfo::Other),
codex_error_info: Some(CodexErrorInfo::Other),
}),
});