diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 86a711e4..35d2c92e 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -252,6 +252,14 @@ impl ModelClient { .and_then(|v| v.to_str().ok()) .and_then(|s| s.parse::().ok()); + if status == StatusCode::UNAUTHORIZED { + if let Some(a) = auth.as_ref() { + let _ = a.refresh_token().await; + } + // Retry immediately with refreshed credentials. + continue; + } + // The OpenAI Responses endpoint returns structured JSON bodies even for 4xx/5xx // errors. When we bubble early with only the HTTP status the caller sees an opaque // "unexpected status 400 Bad Request" which makes debugging nearly impossible. diff --git a/codex-rs/login/src/lib.rs b/codex-rs/login/src/lib.rs index 327daa7e..d6fa4aef 100644 --- a/codex-rs/login/src/lib.rs +++ b/codex-rs/login/src/lib.rs @@ -62,6 +62,39 @@ impl CodexAuth { } } + pub async fn refresh_token(&self) -> Result { + let token_data = self + .get_current_token_data() + .ok_or(std::io::Error::other("Token data is not available."))?; + let token = token_data.refresh_token; + + let refresh_response = try_refresh_token(token) + .await + .map_err(std::io::Error::other)?; + + let updated = update_tokens( + &self.auth_file, + refresh_response.id_token, + refresh_response.access_token, + refresh_response.refresh_token, + ) + .await?; + + if let Ok(mut auth_lock) = self.auth_dot_json.lock() { + *auth_lock = Some(updated.clone()); + } + + let access = match updated.tokens { + Some(t) => t.access_token, + None => { + return Err(std::io::Error::other( + "Token data is not available after refresh.", + )); + } + }; + Ok(access) + } + /// Loads the available auth information from the auth.json or /// OPENAI_API_KEY environment variable. pub fn from_codex_home(