mirror of
https://github.com/openai/codex.git
synced 2026-05-22 03:54:18 +00:00
[codex-cli] match web access token refresh window [ci changed_files]
This commit is contained in:
@@ -371,7 +371,10 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
Some(chatgpt_base_url.clone()),
|
||||
)
|
||||
.await;
|
||||
if let Err(err) = cloud_auth_manager.refresh_managed_chatgpt_token().await {
|
||||
if let Err(err) = cloud_auth_manager
|
||||
.refresh_managed_chatgpt_token_if_near_expiry()
|
||||
.await
|
||||
{
|
||||
warn!("failed to proactively refresh ChatGPT access token during CLI startup: {err}");
|
||||
}
|
||||
let cloud_requirements = cloud_requirements_loader(
|
||||
|
||||
@@ -84,6 +84,7 @@ struct ChatgptAuthState {
|
||||
}
|
||||
|
||||
const TOKEN_REFRESH_INTERVAL: i64 = 8;
|
||||
const CHATGPT_ACCESS_TOKEN_REFRESH_WINDOW_MINUTES: i64 = 5;
|
||||
|
||||
const REFRESH_TOKEN_EXPIRED_MESSAGE: &str = "Your access token could not be refreshed because your refresh token has expired. Please log out and sign in again.";
|
||||
const REFRESH_TOKEN_REUSED_MESSAGE: &str = "Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.";
|
||||
@@ -1715,13 +1716,27 @@ impl AuthManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh managed ChatGPT auth even when its access token has not expired yet.
|
||||
/// Refresh managed ChatGPT auth when its access token is nearly expired.
|
||||
///
|
||||
/// CLI startup uses this to begin sessions with a freshly issued access token.
|
||||
/// Other auth modes either cannot be refreshed locally or have separate refresh
|
||||
/// ownership, so they intentionally no-op here.
|
||||
pub async fn refresh_managed_chatgpt_token(&self) -> Result<(), RefreshTokenError> {
|
||||
if !matches!(self.auth_cached(), Some(CodexAuth::Chatgpt(_))) {
|
||||
/// CLI startup uses the same five-minute refresh window as ChatGPT web so a
|
||||
/// new session does not begin with a token that is about to expire. Other auth
|
||||
/// modes either cannot be refreshed locally or have separate refresh ownership,
|
||||
/// so they intentionally no-op here.
|
||||
pub async fn refresh_managed_chatgpt_token_if_near_expiry(
|
||||
&self,
|
||||
) -> Result<(), RefreshTokenError> {
|
||||
let Some(CodexAuth::Chatgpt(chatgpt_auth)) = self.auth_cached() else {
|
||||
return Ok(());
|
||||
};
|
||||
let should_refresh = chatgpt_auth
|
||||
.current_token_data()
|
||||
.and_then(|tokens| parse_jwt_expiration(&tokens.access_token).ok().flatten())
|
||||
.is_some_and(|expires_at| {
|
||||
expires_at
|
||||
<= Utc::now()
|
||||
+ chrono::Duration::minutes(CHATGPT_ACCESS_TOKEN_REFRESH_WINDOW_MINUTES)
|
||||
});
|
||||
if !should_refresh {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ async fn refresh_token_refreshes_when_auth_is_unchanged() -> Result<()> {
|
||||
|
||||
#[serial_test::serial(auth_refresh)]
|
||||
#[tokio::test]
|
||||
async fn refresh_managed_chatgpt_token_refreshes_even_when_auth_is_fresh() -> Result<()> {
|
||||
async fn refresh_managed_chatgpt_token_refreshes_when_auth_is_near_expiry() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = MockServer::start().await;
|
||||
@@ -176,7 +176,8 @@ async fn refresh_managed_chatgpt_token_refreshes_even_when_auth_is_fresh() -> Re
|
||||
|
||||
let ctx = RefreshTokenTestContext::new(&server).await?;
|
||||
let initial_last_refresh = Utc::now();
|
||||
let initial_tokens = build_tokens(INITIAL_ACCESS_TOKEN, INITIAL_REFRESH_TOKEN);
|
||||
let near_expiry_access_token = access_token_with_expiration(Utc::now() + Duration::minutes(4));
|
||||
let initial_tokens = build_tokens(&near_expiry_access_token, INITIAL_REFRESH_TOKEN);
|
||||
let initial_auth = AuthDotJson {
|
||||
auth_mode: Some(AuthMode::Chatgpt),
|
||||
openai_api_key: None,
|
||||
@@ -187,7 +188,7 @@ async fn refresh_managed_chatgpt_token_refreshes_even_when_auth_is_fresh() -> Re
|
||||
ctx.write_auth(&initial_auth).await?;
|
||||
|
||||
ctx.auth_manager
|
||||
.refresh_managed_chatgpt_token()
|
||||
.refresh_managed_chatgpt_token_if_near_expiry()
|
||||
.await
|
||||
.context("managed ChatGPT refresh should succeed")?;
|
||||
|
||||
@@ -212,6 +213,37 @@ async fn refresh_managed_chatgpt_token_refreshes_even_when_auth_is_fresh() -> Re
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[serial_test::serial(auth_refresh)]
|
||||
#[tokio::test]
|
||||
async fn refresh_managed_chatgpt_token_skips_auth_outside_refresh_window() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let server = MockServer::start().await;
|
||||
let ctx = RefreshTokenTestContext::new(&server).await?;
|
||||
let initial_last_refresh = Utc::now();
|
||||
let fresh_access_token = access_token_with_expiration(Utc::now() + Duration::minutes(6));
|
||||
let initial_tokens = build_tokens(&fresh_access_token, INITIAL_REFRESH_TOKEN);
|
||||
let initial_auth = AuthDotJson {
|
||||
auth_mode: Some(AuthMode::Chatgpt),
|
||||
openai_api_key: None,
|
||||
tokens: Some(initial_tokens.clone()),
|
||||
last_refresh: Some(initial_last_refresh),
|
||||
agent_identity: None,
|
||||
};
|
||||
ctx.write_auth(&initial_auth).await?;
|
||||
|
||||
ctx.auth_manager
|
||||
.refresh_managed_chatgpt_token_if_near_expiry()
|
||||
.await
|
||||
.context("managed ChatGPT refresh should no-op")?;
|
||||
|
||||
assert_eq!(ctx.load_auth()?, initial_auth);
|
||||
let requests = server.received_requests().await.unwrap_or_default();
|
||||
assert!(requests.is_empty(), "expected no refresh token requests");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[serial_test::serial(auth_refresh)]
|
||||
#[tokio::test]
|
||||
async fn refresh_token_skips_refresh_when_auth_changed() -> Result<()> {
|
||||
|
||||
@@ -994,7 +994,10 @@ pub async fn run_main(
|
||||
Some(chatgpt_base_url.clone()),
|
||||
)
|
||||
.await;
|
||||
if let Err(err) = cloud_auth_manager.refresh_managed_chatgpt_token().await {
|
||||
if let Err(err) = cloud_auth_manager
|
||||
.refresh_managed_chatgpt_token_if_near_expiry()
|
||||
.await
|
||||
{
|
||||
warn!("failed to proactively refresh ChatGPT access token during CLI startup: {err}");
|
||||
}
|
||||
let cloud_requirements = cloud_requirements_loader(
|
||||
|
||||
Reference in New Issue
Block a user