Compare commits

...

1 Commits

Author SHA1 Message Date
celia-oai
c6ab9af637 changes 2026-03-25 14:27:17 -07:00
4 changed files with 74 additions and 4 deletions

2
codex-rs/Cargo.lock generated
View File

@@ -2418,8 +2418,6 @@ version = "0.0.0"
dependencies = [
"codex-utils-absolute-path",
"codex-utils-plugins",
"serde",
"serde_json",
"thiserror 2.0.18",
]

View File

@@ -106,6 +106,7 @@ use crate::model_provider_info::WireApi;
use crate::response_debug_context::extract_response_debug_context;
use crate::response_debug_context::extract_response_debug_context_from_api_error;
use crate::response_debug_context::telemetry_api_error_message;
use codex_login::auth::RefreshTokenFailedError;
use crate::response_debug_context::telemetry_transport_error_message;
use crate::tools::spec::create_tools_json_for_responses_api;
use crate::util::FeedbackRequestTags;
@@ -1587,6 +1588,7 @@ async fn handle_unauthorized(
Ok(UnauthorizedRecoveryExecution { mode, phase })
}
Err(RefreshTokenError::Permanent(failed)) => {
let recovery_reason = format_permanent_refresh_recovery_reason(&failed);
session_telemetry.record_auth_recovery(
mode,
phase,
@@ -1595,7 +1597,7 @@ async fn handle_unauthorized(
debug.cf_ray.as_deref(),
debug.auth_error.as_deref(),
debug.auth_error_code.as_deref(),
/*recovery_reason*/ None,
Some(recovery_reason.as_str()),
/*auth_state_changed*/ None,
);
emit_feedback_auth_recovery_tags(
@@ -1610,6 +1612,7 @@ async fn handle_unauthorized(
Err(CodexErr::RefreshTokenFailed(failed))
}
Err(RefreshTokenError::Transient(other)) => {
let recovery_reason = format_transient_refresh_recovery_reason(&other);
session_telemetry.record_auth_recovery(
mode,
phase,
@@ -1618,7 +1621,7 @@ async fn handle_unauthorized(
debug.cf_ray.as_deref(),
debug.auth_error.as_deref(),
debug.auth_error_code.as_deref(),
/*recovery_reason*/ None,
Some(recovery_reason.as_str()),
/*auth_state_changed*/ None,
);
emit_feedback_auth_recovery_tags(
@@ -1667,6 +1670,18 @@ async fn handle_unauthorized(
Err(map_api_error(ApiError::Transport(transport)))
}
fn format_permanent_refresh_recovery_reason(failed: &RefreshTokenFailedError) -> String {
format!(
"failed_reason={}; message={}",
failed.reason.as_str(),
failed.message
)
}
fn format_transient_refresh_recovery_reason(error: &std::io::Error) -> String {
format!("message={error}")
}
fn api_error_http_status(error: &ApiError) -> Option<u16> {
match error {
ApiError::Transport(TransportError::Http { status, .. }) => Some(status.as_u16()),

View File

@@ -2,6 +2,10 @@ use super::AuthRequestTelemetryContext;
use super::ModelClient;
use super::PendingUnauthorizedRetry;
use super::UnauthorizedRecoveryExecution;
use super::format_permanent_refresh_recovery_reason;
use super::format_transient_refresh_recovery_reason;
use codex_login::auth::RefreshTokenFailedError;
use codex_login::auth::RefreshTokenFailedReason;
use codex_otel::SessionTelemetry;
use codex_protocol::ThreadId;
use codex_protocol::openai_models::ModelInfo;
@@ -115,3 +119,45 @@ fn auth_request_telemetry_context_tracks_attached_auth_and_retry_phase() {
assert_eq!(auth_context.recovery_mode, Some("managed"));
assert_eq!(auth_context.recovery_phase, Some("refresh_token"));
}
#[test]
fn refresh_token_failed_reason_label_uses_stable_values() {
assert_eq!(
RefreshTokenFailedReason::Expired.as_str(),
"expired"
);
assert_eq!(
RefreshTokenFailedReason::Exhausted.as_str(),
"exhausted"
);
assert_eq!(
RefreshTokenFailedReason::Revoked.as_str(),
"revoked"
);
assert_eq!(
RefreshTokenFailedReason::Other.as_str(),
"other"
);
}
#[test]
fn refresh_token_error_formats_permanent_recovery_reason() {
let error = RefreshTokenFailedError::new(
RefreshTokenFailedReason::Expired,
"refresh token expired",
);
let reason = format_permanent_refresh_recovery_reason(&error);
assert_eq!(
reason,
"failed_reason=expired; message=refresh token expired"
);
}
#[test]
fn refresh_token_error_formats_transient_recovery_reason() {
let error = std::io::Error::other("temporary refresh failure");
let reason = format_transient_refresh_recovery_reason(&error);
assert_eq!(reason, "message=temporary refresh failure");
}

View File

@@ -23,3 +23,14 @@ pub enum RefreshTokenFailedReason {
Revoked,
Other,
}
impl RefreshTokenFailedReason {
pub fn as_str(self) -> &'static str {
match self {
Self::Expired => "expired",
Self::Exhausted => "exhausted",
Self::Revoked => "revoked",
Self::Other => "other",
}
}
}