From b0f977c3a9c2e68e46e58e589e98f5297d2c8c10 Mon Sep 17 00:00:00 2001 From: jif Date: Fri, 30 Oct 2026 16:09:14 +0000 Subject: [PATCH] Fix unsettled refresh detection after workspace switching (#58381) ## Why Workspace switching rewrites claims returned by `get_token_data()`, so comparing those tokens with the stored refresh generation can fail to recognize an unfinished ChatGPT refresh. ## What changed Compare tokens from the current auth JSON with the unsettled refresh generation, preserving detection when workspace claims differ from the original ID token. ## Testing Extend the refresh cancellation regression test to cover a workspace switch that retains the original ID and refresh tokens. GitOrigin-RevId: 097311e4cdc22cde875589ef616daf60c6b8ef80 --- codex-rs/login/src/auth/auth_tests.rs | 14 +++++++++----- codex-rs/login/src/auth/manager.rs | 10 +++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/codex-rs/login/src/auth/auth_tests.rs b/codex-rs/login/src/auth/auth_tests.rs index bd811d6d56..22747c79bf 100644 --- a/codex-rs/login/src/auth/auth_tests.rs +++ b/codex-rs/login/src/auth/auth_tests.rs @@ -73,14 +73,18 @@ async fn cancelled_refresh_marks_only_started_exchanges_as_unsettled() -> anyhow let server = MockServer::start().await; let storage = FileAuthStorage::new(home.path().to_path_buf()); let mut stored = storage.load()?.context("stored credentials")?; - stored - .tokens - .as_mut() - .context("stored tokens")? - .oauth_provenance = Some(AccountOAuthProvenance { + let tokens = stored.tokens.as_mut().context("stored tokens")?; + tokens.oauth_provenance = Some(AccountOAuthProvenance { issuer: server.uri(), client_id: "codex-client".into(), }); + // Workspace switching retains the original ID and refresh tokens. + tokens.account_id = Some(WORKSPACE_ID_SECOND_ALLOWED.into()); + tokens.access_token = fake_jwt_for_auth_file_params(&AuthFileParams { + openai_api_key: None, + chatgpt_plan_type: Some("business".into()), + chatgpt_account_id: Some(WORKSPACE_ID_SECOND_ALLOWED.into()), + })?; storage.save(&stored)?; let manager = Arc::new( AuthManager::new_from_auth_config( diff --git a/codex-rs/login/src/auth/manager.rs b/codex-rs/login/src/auth/manager.rs index 9b51c70be1..ced49b3dc6 100644 --- a/codex-rs/login/src/auth/manager.rs +++ b/codex-rs/login/src/auth/manager.rs @@ -2587,9 +2587,13 @@ impl AuthManager { .unsettled_chatgpt_refresh .lock() .map_err(|_| std::io::Error::other("Refresh progress is unavailable."))?; - Ok(unsettled - .as_ref() - .is_some_and(|tokens| auth.get_token_data().as_ref().ok() == Some(tokens))) + // Match the stored generation; get_token_data() rewrites workspace claims. + Ok(unsettled.as_ref().is_some_and(|tokens| { + auth.get_current_auth_json() + .and_then(|auth| auth.tokens) + .as_ref() + == Some(tokens) + })) } /// Current cached auth (clone). May be `None` if not logged in or load failed.