Skip to content

Commit b1960cc

Browse files
author
Mateusz
committed
feat(scripts): add clear-reauth for managed Codex OAuth false positives
Adds clear-reauth (all | account_id | email) to clear needs_reauth and consecutive_auth_failures without changing tokens. Made-with: Cursor
1 parent d48e3ea commit b1960cc

1 file changed

Lines changed: 110 additions & 0 deletions

File tree

scripts/manage_openai_codex_accounts.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
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

3641
from __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+
269366
def 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

368478
if __name__ == "__main__":

0 commit comments

Comments
 (0)