@@ -19,7 +19,7 @@ import {
1919 type HybridSelectionOptions ,
2020} from "./rotation.js" ;
2121import { nowMs } from "./utils.js" ;
22- import { ERROR_MESSAGES } from "./constants.js" ;
22+ import { ERROR_MESSAGES , HTTP_STATUS } from "./constants.js" ;
2323import { CodexAuthError } from "./errors.js" ;
2424import {
2525 loadCodexCliState ,
@@ -159,6 +159,37 @@ function findAccountIndexByIdentity<
159159 return undefined ;
160160}
161161
162+ const RETRYABLE_AUTH_PERSISTENCE_CODES = new Set ( [ "EAGAIN" , "EBUSY" , "EPERM" ] ) ;
163+
164+ function isRetryableAuthPersistenceError ( error : unknown ) : boolean {
165+ if ( ! error || typeof error !== "object" ) {
166+ return false ;
167+ }
168+
169+ const candidate = error as {
170+ code ?: unknown ;
171+ status ?: unknown ;
172+ cause ?: unknown ;
173+ } ;
174+ const code =
175+ typeof candidate . code === "string"
176+ ? candidate . code . toUpperCase ( )
177+ : undefined ;
178+ if ( code && RETRYABLE_AUTH_PERSISTENCE_CODES . has ( code ) ) {
179+ return true ;
180+ }
181+
182+ if ( candidate . status === HTTP_STATUS . TOO_MANY_REQUESTS ) {
183+ return true ;
184+ }
185+
186+ if ( candidate . cause && candidate . cause !== error ) {
187+ return isRetryableAuthPersistenceError ( candidate . cause ) ;
188+ }
189+
190+ return false ;
191+ }
192+
162193export interface Workspace {
163194 id : string ;
164195 name ?: string ;
@@ -845,25 +876,68 @@ export class AccountManager {
845876 delete storedAccount . coolingDownUntil ;
846877 delete storedAccount . cooldownReason ;
847878
848- await persist ( nextStorage ) ;
849-
850879 const liveAccount = this . getAccountByIdentity ( source , auth ) ;
851- if ( ! liveAccount ) {
852- log . warn ( "Unable to resolve refreshed live account after persistence" , {
853- sourceIndex : source . index ,
854- } ) ;
855- return null ;
880+ if ( liveAccount ) {
881+ const previousLiveAccountState = {
882+ access : liveAccount . access ,
883+ refreshToken : liveAccount . refreshToken ,
884+ expires : liveAccount . expires ,
885+ accountId : liveAccount . accountId ,
886+ accountIdSource : liveAccount . accountIdSource ,
887+ email : liveAccount . email ,
888+ enabled : liveAccount . enabled ,
889+ coolingDownUntil : liveAccount . coolingDownUntil ,
890+ cooldownReason : liveAccount . cooldownReason ,
891+ consecutiveAuthFailures : liveAccount . consecutiveAuthFailures ,
892+ } ;
893+
894+ this . updateFromAuth ( liveAccount , auth ) ;
895+ liveAccount . enabled = true ;
896+ this . clearAccountCooldown ( liveAccount ) ;
897+ this . clearAuthFailures ( liveAccount ) ;
898+
899+ try {
900+ await persist ( nextStorage ) ;
901+ } catch ( error ) {
902+ liveAccount . access = previousLiveAccountState . access ;
903+ liveAccount . refreshToken = previousLiveAccountState . refreshToken ;
904+ liveAccount . expires = previousLiveAccountState . expires ;
905+ liveAccount . accountId = previousLiveAccountState . accountId ;
906+ liveAccount . accountIdSource =
907+ previousLiveAccountState . accountIdSource ;
908+ liveAccount . email = previousLiveAccountState . email ;
909+ liveAccount . enabled = previousLiveAccountState . enabled ;
910+ liveAccount . consecutiveAuthFailures =
911+ previousLiveAccountState . consecutiveAuthFailures ;
912+ if (
913+ previousLiveAccountState . coolingDownUntil === undefined
914+ ) {
915+ delete liveAccount . coolingDownUntil ;
916+ } else {
917+ liveAccount . coolingDownUntil =
918+ previousLiveAccountState . coolingDownUntil ;
919+ }
920+ if ( previousLiveAccountState . cooldownReason === undefined ) {
921+ delete liveAccount . cooldownReason ;
922+ } else {
923+ liveAccount . cooldownReason =
924+ previousLiveAccountState . cooldownReason ;
925+ }
926+ throw error ;
927+ }
928+
929+ return liveAccount ;
856930 }
857931
858- this . updateFromAuth ( liveAccount , auth ) ;
859- liveAccount . enabled = true ;
860- this . clearAccountCooldown ( liveAccount ) ;
861- this . clearAuthFailures ( liveAccount ) ;
862- return liveAccount ;
932+ await persist ( nextStorage ) ;
933+ log . warn ( "Unable to resolve refreshed live account after persistence" , {
934+ sourceIndex : source . index ,
935+ } ) ;
936+ return null ;
863937 } ) ;
864938 } catch ( error ) {
865939 throw new CodexAuthError ( ERROR_MESSAGES . TOKEN_REFRESH_FAILED , {
866- retryable : true ,
940+ retryable : isRetryableAuthPersistenceError ( error ) ,
867941 cause : error ,
868942 } ) ;
869943 }
0 commit comments