mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
fix(remote-control): retry after auth recovery (#23775)
## Why When remote control hits an auth failure such as a revoked or reused refresh token, the websocket loop falls into reconnect backoff. If the user fixes auth while that loop is sleeping, remote control can stay offline until the old retry timer expires because nothing wakes the loop or resets its exhausted auth recovery state. ## What Changed Added an auth-change watch on `AuthManager` for refresh-relevant cached auth updates. The remote-control websocket loop now subscribes to that signal, resets `UnauthorizedRecovery` and reconnect backoff when auth changes, and retries immediately instead of waiting for the previous delay. Updated the remote-control transport test to verify that reloading auth with the now-available account id wakes enrollment before the prior retry delay. ## Verification `cargo test -p codex-app-server-transport remote_control_waits_for_account_id_before_enrolling`
This commit is contained in:
1 parent
05cf2fc4ce
commit
58be470d15
4 files changed
+153
-21
No files matched your search
@@ -15,6 +15,7 @@ use std::sync::RwLock;
|
||||
use std::sync::atomic::AtomicU64;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio::sync::watch;
|
||||
|
||||
use codex_agent_identity::decode_agent_identity_jwt;
|
||||
use codex_agent_identity::fetch_agent_identity_jwks;
|
||||
@@ -1252,6 +1253,7 @@ impl UnauthorizedRecovery {
|
||||
pub struct AuthManager {
|
||||
codex_home: PathBuf,
|
||||
inner: RwLock<CachedAuth>,
|
||||
auth_change_tx: watch::Sender<u64>,
|
||||
enable_codex_api_key_env: bool,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode,
|
||||
forced_chatgpt_workspace_id: RwLock<Option<Vec<String>>>,
|
||||
@@ -1320,12 +1322,14 @@ impl AuthManager {
|
||||
.await
|
||||
.ok()
|
||||
.flatten();
|
||||
let (auth_change_tx, _auth_change_rx) = watch::channel(0);
|
||||
Self {
|
||||
codex_home,
|
||||
inner: RwLock::new(CachedAuth {
|
||||
auth: managed_auth,
|
||||
permanent_refresh_failure: None,
|
||||
}),
|
||||
auth_change_tx,
|
||||
enable_codex_api_key_env,
|
||||
auth_credentials_store_mode,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
@@ -1341,10 +1345,12 @@ impl AuthManager {
|
||||
auth: Some(auth),
|
||||
permanent_refresh_failure: None,
|
||||
};
|
||||
let (auth_change_tx, _auth_change_rx) = watch::channel(0);
|
||||
|
||||
Arc::new(Self {
|
||||
codex_home: PathBuf::from("non-existent"),
|
||||
inner: RwLock::new(cached),
|
||||
auth_change_tx,
|
||||
enable_codex_api_key_env: false,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
@@ -1360,9 +1366,11 @@ impl AuthManager {
|
||||
auth: Some(auth),
|
||||
permanent_refresh_failure: None,
|
||||
};
|
||||
let (auth_change_tx, _auth_change_rx) = watch::channel(0);
|
||||
Arc::new(Self {
|
||||
codex_home,
|
||||
inner: RwLock::new(cached),
|
||||
auth_change_tx,
|
||||
enable_codex_api_key_env: false,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
@@ -1373,12 +1381,14 @@ impl AuthManager {
|
||||
}
|
||||
|
||||
pub fn external_bearer_only(config: ModelProviderAuthInfo) -> Arc<Self> {
|
||||
let (auth_change_tx, _auth_change_rx) = watch::channel(0);
|
||||
Arc::new(Self {
|
||||
codex_home: PathBuf::from("non-existent"),
|
||||
inner: RwLock::new(CachedAuth {
|
||||
auth: None,
|
||||
permanent_refresh_failure: None,
|
||||
}),
|
||||
auth_change_tx,
|
||||
enable_codex_api_key_env: false,
|
||||
auth_credentials_store_mode: AuthCredentialsStoreMode::File,
|
||||
forced_chatgpt_workspace_id: RwLock::new(None),
|
||||
@@ -1395,6 +1405,11 @@ impl AuthManager {
|
||||
self.inner.read().ok().and_then(|c| c.auth.clone())
|
||||
}
|
||||
|
||||
/// Subscribes to cached auth changes that can affect request recovery.
|
||||
pub fn auth_change_receiver(&self) -> watch::Receiver<u64> {
|
||||
self.auth_change_tx.subscribe()
|
||||
}
|
||||
|
||||
pub fn refresh_failure_for_auth(&self, auth: &CodexAuth) -> Option<RefreshTokenFailedError> {
|
||||
self.inner.read().ok().and_then(|cached| {
|
||||
cached
|
||||
@@ -1537,6 +1552,9 @@ impl AuthManager {
|
||||
}
|
||||
tracing::info!("Reloaded auth, changed: {changed}");
|
||||
guard.auth = new_auth;
|
||||
if auth_changed_for_refresh {
|
||||
self.auth_change_tx.send_modify(|revision| *revision += 1);
|
||||
}
|
||||
changed
|
||||
} else {
|
||||
false
|
||||
|
||||
Reference in new issue
Block a user