From c6ab9af63724c0843de2a191766e95af980cc0d9 Mon Sep 17 00:00:00 2001 From: celia-oai Date: Wed, 25 Mar 2026 14:27:17 -0700 Subject: [PATCH] changes --- codex-rs/Cargo.lock | 2 -- codex-rs/core/src/client.rs | 19 +++++++++++-- codex-rs/core/src/client_tests.rs | 46 +++++++++++++++++++++++++++++++ codex-rs/login/src/auth/error.rs | 11 ++++++++ 4 files changed, 74 insertions(+), 4 deletions(-) diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index df44a7f83b..61b328d84c 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2418,8 +2418,6 @@ version = "0.0.0" dependencies = [ "codex-utils-absolute-path", "codex-utils-plugins", - "serde", - "serde_json", "thiserror 2.0.18", ] diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index e38ff3a562..70e572a834 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -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 { match error { ApiError::Transport(TransportError::Http { status, .. }) => Some(status.as_u16()), diff --git a/codex-rs/core/src/client_tests.rs b/codex-rs/core/src/client_tests.rs index 2c07b4fd1d..bf96fa4a5e 100644 --- a/codex-rs/core/src/client_tests.rs +++ b/codex-rs/core/src/client_tests.rs @@ -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"); +} diff --git a/codex-rs/login/src/auth/error.rs b/codex-rs/login/src/auth/error.rs index fcbd4c7093..af50063095 100644 --- a/codex-rs/login/src/auth/error.rs +++ b/codex-rs/login/src/auth/error.rs @@ -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", + } + } +}