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:
@@ -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
|
||||
|
||||
@@ -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<Duration>,
|
||||
request_id: Option<String>,
|
||||
},
|
||||
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,
|
||||
|
||||
@@ -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<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)]
|
||||
pub struct UsageLimitReachedError {
|
||||
pub(crate) plan_type: Option<PlanType>,
|
||||
|
||||
Reference in New Issue
Block a user