From a97388263d9be68c6636ceb09c670529a0fdf125 Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Wed, 1 Oct 2025 11:58:00 -0700 Subject: [PATCH] Include request ID in the error message --- codex-rs/core/src/chat_completions.rs | 13 +++++-- codex-rs/core/src/client.rs | 29 ++++++++++----- codex-rs/core/src/error.rs | 51 ++++++++++++++++++++++++--- 3 files changed, 79 insertions(+), 14 deletions(-) diff --git a/codex-rs/core/src/chat_completions.rs b/codex-rs/core/src/chat_completions.rs index f7194a0853..feedf3c4e5 100644 --- a/codex-rs/core/src/chat_completions.rs +++ b/codex-rs/core/src/chat_completions.rs @@ -6,6 +6,8 @@ use crate::client_common::ResponseEvent; use crate::client_common::ResponseStream; use crate::error::CodexErr; use crate::error::Result; +use crate::error::RetryLimitReachedError; +use crate::error::UnexpectedResponseError; use crate::model_family::ModelFamily; use crate::openai_tools::create_tools_json_for_chat_completions_api; use crate::util::backoff; @@ -320,11 +322,18 @@ pub(crate) async fn stream_chat_completions( let status = res.status(); if !(status == StatusCode::TOO_MANY_REQUESTS || status.is_server_error()) { let body = (res.text().await).unwrap_or_default(); - return Err(CodexErr::UnexpectedStatus(status, body)); + return Err(CodexErr::UnexpectedStatus(UnexpectedResponseError { + status, + body, + request_id: None, + })); } if attempt > max_retries { - return Err(CodexErr::RetryLimit(status)); + return Err(CodexErr::RetryLimit(RetryLimitReachedError { + status, + request_id: None, + })); } let retry_after_secs = res diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 9e0c1c0430..7cf60f56b9 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -5,6 +5,8 @@ use std::time::Duration; use crate::AuthManager; use crate::auth::CodexAuth; +use crate::error::RetryLimitReachedError; +use crate::error::UnexpectedResponseError; use bytes::Bytes; use codex_app_server_protocol::AuthMode; use codex_protocol::ConversationId; @@ -307,14 +309,17 @@ impl ModelClient { .log_request(attempt, || req_builder.send()) .await; + let mut request_id = None; if let Ok(resp) = &res { + request_id = resp + .headers() + .get("cf-ray") + .map(|v| v.to_str().unwrap_or_default().to_string()); + trace!( - "Response status: {}, cf-ray: {}", + "Response status: {}, cf-ray: {:?}", resp.status(), - resp.headers() - .get("cf-ray") - .map(|v| v.to_str().unwrap_or_default()) - .unwrap_or_default() + request_id ); } @@ -374,7 +379,11 @@ impl ModelClient { // Surface the error body to callers. Use `unwrap_or_default` per Clippy. let body = res.text().await.unwrap_or_default(); return Err(StreamAttemptError::Fatal(CodexErr::UnexpectedStatus( - status, body, + UnexpectedResponseError { + status, + body, + request_id: None, + }, ))); } @@ -405,6 +414,7 @@ impl ModelClient { Err(StreamAttemptError::RetryableHttpError { status, retry_after, + request_id, }) } Err(e) => Err(StreamAttemptError::RetryableTransportError(e.into())), @@ -448,6 +458,7 @@ enum StreamAttemptError { RetryableHttpError { status: StatusCode, retry_after: Option, + request_id: Option, }, RetryableTransportError(CodexErr), Fatal(CodexErr), @@ -472,11 +483,13 @@ impl StreamAttemptError { fn into_error(self) -> CodexErr { match self { - Self::RetryableHttpError { status, .. } => { + Self::RetryableHttpError { + status, request_id, .. + } => { if status == StatusCode::INTERNAL_SERVER_ERROR { CodexErr::InternalServerError } else { - CodexErr::RetryLimit(status) + CodexErr::RetryLimit(RetryLimitReachedError { status, request_id }) } } Self::RetryableTransportError(error) => error, diff --git a/codex-rs/core/src/error.rs b/codex-rs/core/src/error.rs index c6cc8940fa..7482c512fb 100644 --- a/codex-rs/core/src/error.rs +++ b/codex-rs/core/src/error.rs @@ -76,8 +76,8 @@ pub enum CodexErr { Interrupted, /// Unexpected HTTP status code. - #[error("unexpected status {0}: {1}")] - UnexpectedStatus(StatusCode, String), + #[error("{0}")] + UnexpectedStatus(UnexpectedResponseError), #[error("{0}")] UsageLimitReached(UsageLimitReachedError), @@ -91,8 +91,8 @@ pub enum CodexErr { InternalServerError, /// Retry limit exceeded. - #[error("exceeded retry limit, last status: {0}")] - RetryLimit(StatusCode), + #[error("{0}")] + RetryLimit(RetryLimitReachedError), /// Agent loop died unexpectedly #[error("internal error; agent loop died unexpectedly")] @@ -135,6 +135,49 @@ pub enum CodexErr { EnvVar(EnvVarError), } +#[derive(Debug)] +pub struct UnexpectedResponseError { + pub status: StatusCode, + pub body: String, + pub request_id: Option, +} + +impl std::fmt::Display for UnexpectedResponseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "unexpected status {}: {}{}", + self.status, + self.body, + self.request_id + .as_ref() + .map(|id| format!(", request id: {id}")) + .unwrap_or_default() + ) + } +} + +impl std::error::Error for UnexpectedResponseError {} +#[derive(Debug)] +pub struct RetryLimitReachedError { + pub status: StatusCode, + pub request_id: Option, +} + +impl std::fmt::Display for RetryLimitReachedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "exceeded retry limit, last status: {}{}", + self.status, + self.request_id + .as_ref() + .map(|id| format!(", request id: {id}")) + .unwrap_or_default() + ) + } +} + #[derive(Debug)] pub struct UsageLimitReachedError { pub(crate) plan_type: Option,