3131
3232 ./.venv/Scripts/python.exe scripts/manage_openai_codex_accounts.py reset all
3333 ./.venv/Scripts/python.exe scripts/manage_openai_codex_accounts.py reset user@example.com
34+
35+ Clear false-positive needs_reauth / auth-failure counters (tokens unchanged):
36+
37+ ./.venv/Scripts/python.exe scripts/manage_openai_codex_accounts.py clear-reauth all
38+ ./.venv/Scripts/python.exe scripts/manage_openai_codex_accounts.py clear-reauth <account_id>
3439"""
3540
3641from __future__ import annotations
@@ -266,6 +271,98 @@ async def cmd_reset(
266271 )
267272
268273
274+ def _account_cleared_reauth_flags (account : ManagedOAuthAccount ) -> ManagedOAuthAccount :
275+ """Return a copy with ``needs_reauth`` false and auth-failure counter reset."""
276+ if not account .needs_reauth and account .consecutive_auth_failures == 0 :
277+ return account
278+ return account .model_copy (
279+ update = {
280+ "needs_reauth" : False ,
281+ "consecutive_auth_failures" : 0 ,
282+ "updated_at" : datetime .now (timezone .utc ).isoformat (),
283+ }
284+ )
285+
286+
287+ async def cmd_clear_reauth (
288+ storage : ManagedOAuthStorageService ,
289+ args : argparse .Namespace ,
290+ ) -> None :
291+ """Clear ``needs_reauth`` and ``consecutive_auth_failures`` (OAuth tokens unchanged)."""
292+ target_raw = args .target .strip ()
293+ if not target_raw :
294+ print (
295+ "Invalid usage: clear-reauth requires a target. "
296+ 'Use "all", an account_id, or an email, e.g. clear-reauth all'
297+ )
298+ sys .exit (1 )
299+
300+ if target_raw .casefold () == "all" :
301+ accounts = await storage .load_all_accounts ()
302+ if not accounts :
303+ print ("No managed OpenAI Codex accounts found; nothing to update." )
304+ return
305+ updated_n = 0
306+ for account in accounts :
307+ cleared = _account_cleared_reauth_flags (account )
308+ if cleared is account :
309+ continue
310+ await storage .save_account (cleared )
311+ updated_n += 1
312+ print (
313+ f"Cleared reauth flags for { cleared .account_id } "
314+ f"({ cleared .email or '-' } )."
315+ )
316+ if updated_n == 0 :
317+ print (f"No accounts had needs_reauth or auth-failure counters set ({ len (accounts )} checked)." )
318+ else :
319+ print (f"Done. Updated { updated_n } account(s)." )
320+ return
321+
322+ by_id = await storage .get_account (target_raw )
323+ if by_id is not None :
324+ cleared = _account_cleared_reauth_flags (by_id )
325+ if cleared is by_id :
326+ print (
327+ f"Account '{ by_id .account_id } ' ({ by_id .email or '-' } ) "
328+ "already has needs_reauth false and zero auth-failure counter."
329+ )
330+ return
331+ await storage .save_account (cleared )
332+ print (f"Cleared reauth flags for { cleared .account_id } ({ cleared .email or '-' } )." )
333+ return
334+
335+ needle = target_raw .casefold ()
336+ accounts = await storage .load_all_accounts ()
337+ matches = [
338+ account for account in accounts if (account .email or "" ).casefold () == needle
339+ ]
340+ if not matches :
341+ print (
342+ f"No managed account found with id { target_raw !r} or email matching "
343+ f"that string."
344+ )
345+ sys .exit (1 )
346+ if len (matches ) > 1 :
347+ ids = ", " .join (sorted (account .account_id for account in matches ))
348+ print (
349+ "Multiple accounts share that email; cannot pick one. "
350+ f"Matching account_id values: { ids } "
351+ )
352+ sys .exit (1 )
353+
354+ account = matches [0 ]
355+ cleared = _account_cleared_reauth_flags (account )
356+ if cleared is account :
357+ print (
358+ f"Account { account .account_id } ({ account .email or '-' } ) "
359+ "already has needs_reauth false and zero auth-failure counter."
360+ )
361+ return
362+ await storage .save_account (cleared )
363+ print (f"Cleared reauth flags for { cleared .account_id } ({ cleared .email or '-' } )." )
364+
365+
269366def main () -> None :
270367 parser = argparse .ArgumentParser (description = "Manage OpenAI Codex OAuth accounts" )
271368 parser .add_argument (
@@ -348,6 +445,17 @@ def main() -> None:
348445 help = 'Use the literal word "all" for every account, or the account email' ,
349446 )
350447
448+ clear_reauth_parser = subparsers .add_parser (
449+ "clear-reauth" ,
450+ help = (
451+ "Clear needs_reauth and consecutive_auth_failures on disk (tokens unchanged)"
452+ ),
453+ )
454+ clear_reauth_parser .add_argument (
455+ "target" ,
456+ help = 'Use "all", a storage account_id, or the account email' ,
457+ )
458+
351459 args = parser .parse_args ()
352460 storage = ManagedOAuthStorageService (args .storage_path )
353461
@@ -363,6 +471,8 @@ def main() -> None:
363471 asyncio .run (cmd_remove (storage , args ))
364472 elif args .command == "reset" :
365473 asyncio .run (cmd_reset (storage , args ))
474+ elif args .command == "clear-reauth" :
475+ asyncio .run (cmd_clear_reauth (storage , args ))
366476
367477
368478if __name__ == "__main__" :
0 commit comments