Include request ID in the error message (#4572)

To help with issue debugging
<img width="1414" height="253" alt="image"
src="https://github.com/user-attachments/assets/254732df-44ac-4252-997a-6c5e0927355b"
/>
This commit is contained in:
pakrym-oai
2025-10-01 15:36:04 -07:00
committed by GitHub
parent 6f97ec4990
commit e899ae7d8a
3 changed files with 79 additions and 14 deletions

View File

@@ -6,6 +6,8 @@ use crate::client_common::ResponseEvent;
use crate::client_common::ResponseStream; use crate::client_common::ResponseStream;
use crate::error::CodexErr; use crate::error::CodexErr;
use crate::error::Result; use crate::error::Result;
use crate::error::RetryLimitReachedError;
use crate::error::UnexpectedResponseError;
use crate::model_family::ModelFamily; use crate::model_family::ModelFamily;
use crate::openai_tools::create_tools_json_for_chat_completions_api; use crate::openai_tools::create_tools_json_for_chat_completions_api;
use crate::util::backoff; use crate::util::backoff;
@@ -320,11 +322,18 @@ pub(crate) async fn stream_chat_completions(
let status = res.status(); let status = res.status();
if !(status == StatusCode::TOO_MANY_REQUESTS || status.is_server_error()) { if !(status == StatusCode::TOO_MANY_REQUESTS || status.is_server_error()) {
let body = (res.text().await).unwrap_or_default(); 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 { if attempt > max_retries {
return Err(CodexErr::RetryLimit(status)); return Err(CodexErr::RetryLimit(RetryLimitReachedError {
status,
request_id: None,
}));
} }
let retry_after_secs = res let retry_after_secs = res

View File

@@ -5,6 +5,8 @@ use std::time::Duration;
use crate::AuthManager; use crate::AuthManager;
use crate::auth::CodexAuth; use crate::auth::CodexAuth;
use crate::error::RetryLimitReachedError;
use crate::error::UnexpectedResponseError;
use bytes::Bytes; use bytes::Bytes;
use codex_app_server_protocol::AuthMode; use codex_app_server_protocol::AuthMode;
use codex_protocol::ConversationId; use codex_protocol::ConversationId;
@@ -307,14 +309,17 @@ impl ModelClient {
.log_request(attempt, || req_builder.send()) .log_request(attempt, || req_builder.send())
.await; .await;
let mut request_id = None;
if let Ok(resp) = &res { if let Ok(resp) = &res {
request_id = resp
.headers()
.get("cf-ray")
.map(|v| v.to_str().unwrap_or_default().to_string());
trace!( trace!(
"Response status: {}, cf-ray: {}", "Response status: {}, cf-ray: {:?}",
resp.status(), resp.status(),
resp.headers() request_id
.get("cf-ray")
.map(|v| v.to_str().unwrap_or_default())
.unwrap_or_default()
); );
} }
@@ -374,7 +379,11 @@ impl ModelClient {
// Surface the error body to callers. Use `unwrap_or_default` per Clippy. // Surface the error body to callers. Use `unwrap_or_default` per Clippy.
let body = res.text().await.unwrap_or_default(); let body = res.text().await.unwrap_or_default();
return Err(StreamAttemptError::Fatal(CodexErr::UnexpectedStatus( return Err(StreamAttemptError::Fatal(CodexErr::UnexpectedStatus(
status, body, UnexpectedResponseError {
status,
body,
request_id: None,
},
))); )));
} }
@@ -405,6 +414,7 @@ impl ModelClient {
Err(StreamAttemptError::RetryableHttpError { Err(StreamAttemptError::RetryableHttpError {
status, status,
retry_after, retry_after,
request_id,
}) })
} }
Err(e) => Err(StreamAttemptError::RetryableTransportError(e.into())), Err(e) => Err(StreamAttemptError::RetryableTransportError(e.into())),
@@ -448,6 +458,7 @@ enum StreamAttemptError {
RetryableHttpError { RetryableHttpError {
status: StatusCode, status: StatusCode,
retry_after: Option<Duration>, retry_after: Option<Duration>,
request_id: Option<String>,
}, },
RetryableTransportError(CodexErr), RetryableTransportError(CodexErr),
Fatal(CodexErr), Fatal(CodexErr),
@@ -472,11 +483,13 @@ impl StreamAttemptError {
fn into_error(self) -> CodexErr { fn into_error(self) -> CodexErr {
match self { match self {
Self::RetryableHttpError { status, .. } => { Self::RetryableHttpError {
status, request_id, ..
} => {
if status == StatusCode::INTERNAL_SERVER_ERROR { if status == StatusCode::INTERNAL_SERVER_ERROR {
CodexErr::InternalServerError CodexErr::InternalServerError
} else { } else {
CodexErr::RetryLimit(status) CodexErr::RetryLimit(RetryLimitReachedError { status, request_id })
} }
} }
Self::RetryableTransportError(error) => error, Self::RetryableTransportError(error) => error,

View File

@@ -76,8 +76,8 @@ pub enum CodexErr {
Interrupted, Interrupted,
/// Unexpected HTTP status code. /// Unexpected HTTP status code.
#[error("unexpected status {0}: {1}")] #[error("{0}")]
UnexpectedStatus(StatusCode, String), UnexpectedStatus(UnexpectedResponseError),
#[error("{0}")] #[error("{0}")]
UsageLimitReached(UsageLimitReachedError), UsageLimitReached(UsageLimitReachedError),
@@ -91,8 +91,8 @@ pub enum CodexErr {
InternalServerError, InternalServerError,
/// Retry limit exceeded. /// Retry limit exceeded.
#[error("exceeded retry limit, last status: {0}")] #[error("{0}")]
RetryLimit(StatusCode), RetryLimit(RetryLimitReachedError),
/// Agent loop died unexpectedly /// Agent loop died unexpectedly
#[error("internal error; agent loop died unexpectedly")] #[error("internal error; agent loop died unexpectedly")]
@@ -135,6 +135,49 @@ pub enum CodexErr {
EnvVar(EnvVarError), EnvVar(EnvVarError),
} }
#[derive(Debug)]
pub struct UnexpectedResponseError {
pub status: StatusCode,
pub body: String,
pub request_id: Option<String>,
}
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<String>,
}
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)] #[derive(Debug)]
pub struct UsageLimitReachedError { pub struct UsageLimitReachedError {
pub(crate) plan_type: Option<PlanType>, pub(crate) plan_type: Option<PlanType>,