From 8c238bb8a207d3b05534a36d0351e5272ade72f0 Mon Sep 17 00:00:00 2001
From: celia-oai <celia@openai.com>
Date: Thu, 20 Nov 2025 15:54:34 -0800
Subject: [PATCH 1/2] changes

---
 codex-rs/app-server/README.md | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md
index 35653099aad4..0c62f17abd3e 100644
--- a/codex-rs/app-server/README.md
+++ b/codex-rs/app-server/README.md
@@ -339,6 +339,26 @@ 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, codexErrorCode? } }`.
+
+- `error` — emitted whenever the server hits an error mid-turn (for example, upstream model errors or quota limits). Carries the same `{ error: { message, codexErrorCode? } }` payload as `turn.status: "failed"` and may precede that terminal notification.
+
+  `codexErrorCode` 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 `codexErrorCode` variant; otherwise it is `null`.
+
 #### Thread items
 
 `ThreadItem` is the tagged union carried in turn responses and `item/*` notifications. Currently we support events for the following items:

From 7528212311a7a33e70bfe3ed7d5eb88bdf37b20e Mon Sep 17 00:00:00 2001
From: celia-oai <celia@openai.com>
Date: Thu, 20 Nov 2025 16:09:23 -0800
Subject: [PATCH 2/2] name fix

---
 .../app-server-protocol/src/protocol/v2.rs    |  7 ++++--
 codex-rs/app-server/README.md                 | 11 ++++++----
 .../app-server/src/bespoke_event_handling.rs  | 22 +++++++++----------
 codex-rs/core/src/codex.rs                    |  6 ++---
 codex-rs/core/src/error.rs                    |  4 ++--
 .../tests/event_processor_with_json_output.rs |  6 ++---
 codex-rs/protocol/src/protocol.rs             |  4 ++--
 codex-rs/tui/src/chatwidget/tests.rs          |  2 +-
 8 files changed, 34 insertions(+), 28 deletions(-)

diff --git a/codex-rs/app-server-protocol/src/protocol/v2.rs b/codex-rs/app-server-protocol/src/protocol/v2.rs
index 6ecf0f925658..1f8463a60a41 100644
--- a/codex-rs/app-server-protocol/src/protocol/v2.rs
+++ b/codex-rs/app-server-protocol/src/protocol/v2.rs
@@ -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),
         };
diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md
index 0c62f17abd3e..d5ac8a57d08a 100644
--- a/codex-rs/app-server/README.md
+++ b/codex-rs/app-server/README.md
@@ -340,11 +340,14 @@ 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, codexErrorCode? } }`.
+- `turn/completed` — `{ turn }` where `turn.status` is `completed`, `interrupted`, or `failed`; failures carry `{ error: { message, codexErrorInfo? } }`.
 
-- `error` — emitted whenever the server hits an error mid-turn (for example, upstream model errors or quota limits). Carries the same `{ error: { message, codexErrorCode? } }` payload as `turn.status: "failed"` and may precede that terminal notification.
+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.
 
-  `codexErrorCode` maps to the `CodexErrorInfo` enum. Common values:
+#### 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
@@ -357,7 +360,7 @@ The app-server streams JSON-RPC notifications while a turn is running. Each turn
   - `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 `codexErrorCode` variant; otherwise it is `null`.
+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
 
diff --git a/codex-rs/app-server/src/bespoke_event_handling.rs b/codex-rs/app-server/src/bespoke_event_handling.rs
index 69346d2e31c9..387aabc32df7 100644
--- a/codex-rs/app-server/src/bespoke_event_handling.rs
+++ b/codex-rs/app-server/src/bespoke_event_handling.rs
@@ -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,
                         }
                     }
                 );
diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs
index 8b8292cd1663..7e93cc9d0a46 100644
--- a/codex-rs/core/src/codex.rs
+++ b/codex-rs/core/src/codex.rs
@@ -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;
diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs
index ee729304997d..9130b40e1e7d 100644
--- a/codex-rs/core/src/error.rs
+++ b/codex-rs/core/src/error.rs
@@ -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)
             })
diff --git a/codex-rs/exec/tests/event_processor_with_json_output.rs b/codex-rs/exec/tests/event_processor_with_json_output.rs
index f716f0f0f4c9..06f99b96b105 100644
--- a/codex-rs/exec/tests/event_processor_with_json_output.rs
+++ b/codex-rs/exec/tests/event_processor_with_json_output.rs
@@ -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!(
diff --git a/codex-rs/protocol/src/protocol.rs b/codex-rs/protocol/src/protocol.rs
index 5806e1a6f031..e0c2e4df3fff 100644
--- a/codex-rs/protocol/src/protocol.rs
+++ b/codex-rs/protocol/src/protocol.rs
@@ -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)]
diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs
index 45a9a8df218f..61e23fb3fcc0 100644
--- a/codex-rs/tui/src/chatwidget/tests.rs
+++ b/codex-rs/tui/src/chatwidget/tests.rs
@@ -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),
         }),
     });
 
