Refresh tokens more often and log a better message when both auth and token refresh fails (#5655)
<img width="784" height="153" alt="image" src="https://github.com/user-attachments/assets/c44b0eb2-d65c-4fc2-8b54-b34f7e1c4d95" />
This commit is contained in:
@@ -25,6 +25,7 @@ use crate::default_client::CodexHttpClient;
|
||||
use crate::token_data::PlanType;
|
||||
use crate::token_data::TokenData;
|
||||
use crate::token_data::parse_id_token;
|
||||
use crate::util::try_parse_error_message;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CodexAuth {
|
||||
@@ -42,6 +43,9 @@ impl PartialEq for CodexAuth {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pakrym): use token exp field to check for expiration instead
|
||||
const TOKEN_REFRESH_INTERVAL: i64 = 8;
|
||||
|
||||
impl CodexAuth {
|
||||
pub async fn refresh_token(&self) -> Result<String, std::io::Error> {
|
||||
tracing::info!("Refreshing token");
|
||||
@@ -94,7 +98,7 @@ impl CodexAuth {
|
||||
last_refresh: Some(last_refresh),
|
||||
..
|
||||
}) => {
|
||||
if last_refresh < Utc::now() - chrono::Duration::days(28) {
|
||||
if last_refresh < Utc::now() - chrono::Duration::days(TOKEN_REFRESH_INTERVAL) {
|
||||
let refresh_response = tokio::time::timeout(
|
||||
Duration::from_secs(60),
|
||||
try_refresh_token(tokens.refresh_token.clone(), &self.client),
|
||||
@@ -446,8 +450,9 @@ async fn try_refresh_token(
|
||||
Ok(refresh_response)
|
||||
} else {
|
||||
Err(std::io::Error::other(format!(
|
||||
"Failed to refresh token: {}",
|
||||
response.status()
|
||||
"Failed to refresh token: {}: {}",
|
||||
response.status(),
|
||||
try_parse_error_message(&response.text().await.unwrap_or_default()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use rand::Rng;
|
||||
use tracing::debug;
|
||||
|
||||
const INITIAL_DELAY_MS: u64 = 200;
|
||||
const BACKOFF_FACTOR: f64 = 2.0;
|
||||
@@ -11,3 +12,47 @@ pub(crate) fn backoff(attempt: u64) -> Duration {
|
||||
let jitter = rand::rng().random_range(0.9..1.1);
|
||||
Duration::from_millis((base as f64 * jitter) as u64)
|
||||
}
|
||||
|
||||
pub(crate) fn try_parse_error_message(text: &str) -> String {
|
||||
debug!("Parsing server error response: {}", text);
|
||||
let json = serde_json::from_str::<serde_json::Value>(text).unwrap_or_default();
|
||||
if let Some(error) = json.get("error")
|
||||
&& let Some(message) = error.get("message")
|
||||
&& let Some(message_str) = message.as_str()
|
||||
{
|
||||
return message_str.to_string();
|
||||
}
|
||||
if text.is_empty() {
|
||||
return "Unknown error".to_string();
|
||||
}
|
||||
text.to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_try_parse_error_message() {
|
||||
let text = r#"{
|
||||
"error": {
|
||||
"message": "Your refresh token has already been used to generate a new access token. Please try signing in again.",
|
||||
"type": "invalid_request_error",
|
||||
"param": null,
|
||||
"code": "refresh_token_reused"
|
||||
}
|
||||
}"#;
|
||||
let message = try_parse_error_message(text);
|
||||
assert_eq!(
|
||||
message,
|
||||
"Your refresh token has already been used to generate a new access token. Please try signing in again."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_try_parse_error_message_no_error() {
|
||||
let text = r#"{"message": "test"}"#;
|
||||
let message = try_parse_error_message(text);
|
||||
assert_eq!(message, r#"{"message": "test"}"#);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user