codex-multi-auth 1.2.6 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +30 -18
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +41 -21
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/accounts.d.ts +47 -0
  6. package/dist/lib/accounts.d.ts.map +1 -1
  7. package/dist/lib/accounts.js +219 -33
  8. package/dist/lib/accounts.js.map +1 -1
  9. package/dist/lib/auth/auth.d.ts +16 -0
  10. package/dist/lib/auth/auth.d.ts.map +1 -1
  11. package/dist/lib/auth/auth.js +52 -6
  12. package/dist/lib/auth/auth.js.map +1 -1
  13. package/dist/lib/auth/server.d.ts +1 -1
  14. package/dist/lib/auth/server.d.ts.map +1 -1
  15. package/dist/lib/auth/server.js +10 -8
  16. package/dist/lib/auth/server.js.map +1 -1
  17. package/dist/lib/codex-manager/commands/status.d.ts.map +1 -1
  18. package/dist/lib/codex-manager/commands/status.js +13 -5
  19. package/dist/lib/codex-manager/commands/status.js.map +1 -1
  20. package/dist/lib/codex-manager/commands/verify.d.ts +59 -0
  21. package/dist/lib/codex-manager/commands/verify.d.ts.map +1 -0
  22. package/dist/lib/codex-manager/commands/verify.js +332 -0
  23. package/dist/lib/codex-manager/commands/verify.js.map +1 -0
  24. package/dist/lib/codex-manager/commands/why-selected.d.ts +57 -0
  25. package/dist/lib/codex-manager/commands/why-selected.d.ts.map +1 -0
  26. package/dist/lib/codex-manager/commands/why-selected.js +198 -0
  27. package/dist/lib/codex-manager/commands/why-selected.js.map +1 -0
  28. package/dist/lib/codex-manager/help.d.ts.map +1 -1
  29. package/dist/lib/codex-manager/help.js +4 -0
  30. package/dist/lib/codex-manager/help.js.map +1 -1
  31. package/dist/lib/codex-manager/settings-hub/backend.d.ts +9 -0
  32. package/dist/lib/codex-manager/settings-hub/backend.d.ts.map +1 -0
  33. package/dist/lib/codex-manager/settings-hub/backend.js +79 -0
  34. package/dist/lib/codex-manager/settings-hub/backend.js.map +1 -0
  35. package/dist/lib/codex-manager/settings-hub/dashboard.d.ts +24 -0
  36. package/dist/lib/codex-manager/settings-hub/dashboard.d.ts.map +1 -0
  37. package/dist/lib/codex-manager/settings-hub/dashboard.js +223 -0
  38. package/dist/lib/codex-manager/settings-hub/dashboard.js.map +1 -0
  39. package/dist/lib/codex-manager/settings-hub/experimental.d.ts +18 -0
  40. package/dist/lib/codex-manager/settings-hub/experimental.d.ts.map +1 -0
  41. package/dist/lib/codex-manager/settings-hub/experimental.js +78 -0
  42. package/dist/lib/codex-manager/settings-hub/experimental.js.map +1 -0
  43. package/dist/lib/codex-manager/settings-hub/index.d.ts +34 -0
  44. package/dist/lib/codex-manager/settings-hub/index.d.ts.map +1 -0
  45. package/dist/lib/codex-manager/settings-hub/index.js +81 -0
  46. package/dist/lib/codex-manager/settings-hub/index.js.map +1 -0
  47. package/dist/lib/codex-manager/settings-hub/shared.d.ts +40 -0
  48. package/dist/lib/codex-manager/settings-hub/shared.d.ts.map +1 -0
  49. package/dist/lib/codex-manager/settings-hub/shared.js +103 -0
  50. package/dist/lib/codex-manager/settings-hub/shared.js.map +1 -0
  51. package/dist/lib/codex-manager/settings-hub.d.ts +1 -64
  52. package/dist/lib/codex-manager/settings-hub.d.ts.map +1 -1
  53. package/dist/lib/codex-manager/settings-hub.js +4 -515
  54. package/dist/lib/codex-manager/settings-hub.js.map +1 -1
  55. package/dist/lib/codex-manager.d.ts.map +1 -1
  56. package/dist/lib/codex-manager.js +95 -13
  57. package/dist/lib/codex-manager.js.map +1 -1
  58. package/dist/lib/config.d.ts +14 -0
  59. package/dist/lib/config.d.ts.map +1 -1
  60. package/dist/lib/config.js +21 -9
  61. package/dist/lib/config.js.map +1 -1
  62. package/dist/lib/health.d.ts.map +1 -1
  63. package/dist/lib/health.js.map +1 -1
  64. package/dist/lib/recovery/storage.d.ts.map +1 -1
  65. package/dist/lib/recovery/storage.js +112 -8
  66. package/dist/lib/recovery/storage.js.map +1 -1
  67. package/dist/lib/request/response-handler.d.ts.map +1 -1
  68. package/dist/lib/request/response-handler.js +67 -23
  69. package/dist/lib/request/response-handler.js.map +1 -1
  70. package/dist/lib/rotation.d.ts +53 -1
  71. package/dist/lib/rotation.d.ts.map +1 -1
  72. package/dist/lib/rotation.js +135 -23
  73. package/dist/lib/rotation.js.map +1 -1
  74. package/dist/lib/routing-mutex.d.ts +107 -0
  75. package/dist/lib/routing-mutex.d.ts.map +1 -0
  76. package/dist/lib/routing-mutex.js +104 -0
  77. package/dist/lib/routing-mutex.js.map +1 -0
  78. package/dist/lib/runtime/storage-scope.d.ts.map +1 -1
  79. package/dist/lib/runtime/storage-scope.js +17 -1
  80. package/dist/lib/runtime/storage-scope.js.map +1 -1
  81. package/dist/lib/schemas.d.ts +125 -0
  82. package/dist/lib/schemas.d.ts.map +1 -1
  83. package/dist/lib/schemas.js +139 -5
  84. package/dist/lib/schemas.js.map +1 -1
  85. package/dist/lib/storage/account-clear.d.ts.map +1 -1
  86. package/dist/lib/storage/account-clear.js +7 -1
  87. package/dist/lib/storage/account-clear.js.map +1 -1
  88. package/dist/lib/storage/flagged-storage-file.d.ts.map +1 -1
  89. package/dist/lib/storage/flagged-storage-file.js +17 -1
  90. package/dist/lib/storage/flagged-storage-file.js.map +1 -1
  91. package/dist/lib/storage/flagged-storage-io.d.ts.map +1 -1
  92. package/dist/lib/storage/flagged-storage-io.js +24 -10
  93. package/dist/lib/storage/flagged-storage-io.js.map +1 -1
  94. package/dist/lib/storage/import-export.d.ts.map +1 -1
  95. package/dist/lib/storage/import-export.js +16 -4
  96. package/dist/lib/storage/import-export.js.map +1 -1
  97. package/dist/lib/storage/paths.d.ts.map +1 -1
  98. package/dist/lib/storage/paths.js +62 -7
  99. package/dist/lib/storage/paths.js.map +1 -1
  100. package/dist/lib/storage/snapshot-inspectors.d.ts.map +1 -1
  101. package/dist/lib/storage/snapshot-inspectors.js +23 -16
  102. package/dist/lib/storage/snapshot-inspectors.js.map +1 -1
  103. package/dist/lib/storage/storage-parser.d.ts +17 -0
  104. package/dist/lib/storage/storage-parser.d.ts.map +1 -1
  105. package/dist/lib/storage/storage-parser.js +30 -1
  106. package/dist/lib/storage/storage-parser.js.map +1 -1
  107. package/dist/lib/storage.d.ts +1 -1
  108. package/dist/lib/storage.d.ts.map +1 -1
  109. package/dist/lib/storage.js +45 -26
  110. package/dist/lib/storage.js.map +1 -1
  111. package/dist/lib/ui/copy.d.ts +1 -1
  112. package/dist/lib/ui/copy.d.ts.map +1 -1
  113. package/dist/lib/ui/copy.js +2 -1
  114. package/dist/lib/ui/copy.js.map +1 -1
  115. package/package.json +5 -5
  116. package/scripts/codex-bin-resolver.js +162 -7
  117. package/scripts/codex-multi-auth.js +1 -1
  118. package/scripts/codex.js +233 -226
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://img.shields.io/npm/v/codex-multi-auth.svg)](https://www.npmjs.com/package/codex-multi-auth)
4
4
  [![npm downloads](https://img.shields.io/npm/dw/codex-multi-auth.svg)](https://www.npmjs.com/package/codex-multi-auth)
5
5
 
6
- Codex CLI-first multi-account OAuth manager for the official `@openai/codex` CLI.
6
+ Codex CLI-first multi-account OAuth manager for the official Codex CLI, including the `@openai/codex` npm launcher and native `codex` installs.
7
7
 
8
8
  <img width="1270" height="729" alt="2026-02-28 12_54_58-prompt txt ‎- Notepads" src="https://github.com/user-attachments/assets/0cecb77e-a6d3-432a-ba48-3577db0c7093" />
9
9
 
@@ -11,6 +11,7 @@ Codex CLI-first multi-account OAuth manager for the official `@openai/codex` CLI
11
11
  > [!NOTE]
12
12
  > Legacy scoped prerelease package `@ndycode/codex-multi-auth` is migration-only.
13
13
  > Use `codex-multi-auth` for all new installs.
14
+
14
15
  ## What You Get
15
16
 
16
17
  - Canonical `codex auth ...` workflow for account login, switching, checks, and diagnostics
@@ -19,7 +20,7 @@ Codex CLI-first multi-account OAuth manager for the official `@openai/codex` CLI
19
20
  - Interactive dashboard for account actions and settings
20
21
  - Experimental settings tab for staged sync, backup, and refresh-guard controls
21
22
  - Forecast, report, fix, and doctor commands for operational safety
22
- - Runtime counters, budget/cooldown state, and multi-auth probe visibility in `codex auth status` / `codex auth report`
23
+ - Runtime counters, budget/cooldown state, and multi-auth probe visibility in `codex auth status` / `codex auth report`
23
24
  - Flagged account verification and restore flow
24
25
  - Session affinity and live account sync controls
25
26
  - Proactive refresh and preemptive quota deferral controls
@@ -71,6 +72,8 @@ codex-multi-auth --version
71
72
  codex auth status
72
73
  ```
73
74
 
75
+ Any official install path is fine as long as `codex` is on `PATH`: `npm i -g @openai/codex`, `brew install --cask codex`, or an official release binary.
76
+
74
77
  </details>
75
78
 
76
79
  <details>
@@ -105,6 +108,13 @@ npm i -g codex-multi-auth
105
108
  codex auth login
106
109
  ```
107
110
 
111
+ If you already installed the official native CLI via Homebrew or a release binary, you only need:
112
+
113
+ ```bash
114
+ npm i -g codex-multi-auth
115
+ codex auth login
116
+ ```
117
+
108
118
  Verify the wrapper and the new account:
109
119
 
110
120
  ```bash
@@ -147,6 +157,7 @@ If browser launch is blocked, use the alternate login paths in [docs/getting-sta
147
157
  | Command | What it answers |
148
158
  | --- | --- |
149
159
  | `codex auth verify-flagged` | Can any previously flagged account be restored? |
160
+ | `codex auth verify --paths` | Do my storage path chain and sandbox probes still pass self-test? |
150
161
  | `codex auth fix --dry-run` | What safe storage or account repairs are available? |
151
162
  | `codex auth doctor --fix` | Can the CLI diagnose and apply the safest fixes now? |
152
163
 
@@ -156,6 +167,7 @@ If browser launch is blocked, use the alternate login paths in [docs/getting-sta
156
167
  | --- | --- |
157
168
  | `codex auth report --live --json` | How do I get the full machine-readable health report? |
158
169
  | `codex auth fix --live --model gpt-5-codex` | How do I run live repair probes with a chosen model? |
170
+ | `codex auth why-selected --json` | Which account does the selector pick now, and why? |
159
171
 
160
172
  ### Reliability behavior
161
173
 
@@ -163,7 +175,7 @@ If browser launch is blocked, use the alternate login paths in [docs/getting-sta
163
175
  - active requests use a bounded outbound request budget so one prompt cannot walk the full pool indefinitely
164
176
  - repeated cross-account 5xx bursts trigger a short cooldown instead of continuing aggressive rotation
165
177
  - proactive refresh is staggered to reduce background refresh bursts
166
- - `codex auth status` surfaces recent runtime request metrics in text output, and `codex auth report --json` exposes the machine-readable cooldown/runtime snapshot
178
+ - `codex auth status` surfaces recent runtime request metrics in text output, and `codex auth report --json` exposes the machine-readable cooldown/runtime snapshot
167
179
 
168
180
  ---
169
181
 
@@ -196,12 +208,12 @@ If browser launch is blocked, use the alternate login paths in [docs/getting-sta
196
208
  | File | Default path |
197
209
  | --- | --- |
198
210
  | Settings | `~/.codex/multi-auth/settings.json` |
199
- | Accounts | `~/.codex/multi-auth/openai-codex-accounts.json` |
200
- | Flagged accounts | `~/.codex/multi-auth/openai-codex-flagged-accounts.json` |
201
- | Quota cache | `~/.codex/multi-auth/quota-cache.json` |
202
- | Runtime observability | `~/.codex/multi-auth/runtime-observability.json` |
203
- | Logs | `~/.codex/multi-auth/logs/codex-plugin/` |
204
- | Per-project accounts | `~/.codex/multi-auth/projects/<project-key>/openai-codex-accounts.json` |
211
+ | Accounts | `~/.codex/multi-auth/openai-codex-accounts.json` |
212
+ | Flagged accounts | `~/.codex/multi-auth/openai-codex-flagged-accounts.json` |
213
+ | Quota cache | `~/.codex/multi-auth/quota-cache.json` |
214
+ | Runtime observability | `~/.codex/multi-auth/runtime-observability.json` |
215
+ | Logs | `~/.codex/multi-auth/logs/codex-plugin/` |
216
+ | Per-project accounts | `~/.codex/multi-auth/projects/<project-key>/openai-codex-accounts.json` |
205
217
 
206
218
  Override root with `CODEX_MULTI_AUTH_DIR=<path>`.
207
219
 
@@ -273,12 +285,12 @@ codex auth login
273
285
  <details>
274
286
  <summary><b>Common symptoms</b></summary>
275
287
 
276
- - `codex auth` unrecognized: run `where codex`, then follow `docs/troubleshooting.md` for routing fallback commands
288
+ - `codex auth` unrecognized: run `where codex` or `which codex`, then follow `docs/troubleshooting.md` for routing fallback commands
277
289
  - Switch succeeds but wrong account appears active: run `codex auth switch <index>`, then restart session
278
- - Requests fail fast with a pool cooldown message: wait for the cooldown window or inspect `codex auth status`
279
- - Requests fail fast after repeated upstream 5xx errors: inspect `codex auth report --json` for runtime traffic and cooldown details
280
- - Storage cleanup fails with `EBUSY` / `EPERM` (Windows): run `codex auth doctor --fix` to retry, or manually remove `~/.codex/multi-auth/<project-key>/` and re-login
281
- - OAuth callback on port `1455` fails: free the port and re-run `codex auth login`
290
+ - Requests fail fast with a pool cooldown message: wait for the cooldown window or inspect `codex auth status`
291
+ - Requests fail fast after repeated upstream 5xx errors: inspect `codex auth report --json` for runtime traffic and cooldown details
292
+ - Storage cleanup fails with `EBUSY` / `EPERM` (Windows): run `codex auth doctor --fix` to retry, or manually remove `~/.codex/multi-auth/<project-key>/` and re-login
293
+ - OAuth callback on port `1455` fails: free the port and re-run `codex auth login`
282
294
  - Browser launch is blocked or you are in a headless shell: re-run `codex auth login --manual` or set `CODEX_AUTH_NO_BROWSER=1`
283
295
  - `missing field id_token` / `token_expired` / `refresh_token_reused`: re-login affected account
284
296
 
@@ -321,10 +333,10 @@ codex auth doctor --json
321
333
 
322
334
  ## Release Notes
323
335
 
324
- - Current stable: [docs/releases/v1.2.6.md](docs/releases/v1.2.6.md)
325
- - Recent patch release: [docs/releases/v1.2.5.md](docs/releases/v1.2.5.md)
326
- - Previous stable: [docs/releases/v1.2.2.md](docs/releases/v1.2.2.md)
327
- - Earlier stable: [docs/releases/v1.2.1.md](docs/releases/v1.2.1.md)
336
+ - Current stable: [docs/releases/v1.3.0.md](docs/releases/v1.3.0.md)
337
+ - Previous stable: [docs/releases/v1.2.6.md](docs/releases/v1.2.6.md)
338
+ - Earlier stable: [docs/releases/v1.2.5.md](docs/releases/v1.2.5.md)
339
+ - Full release archive: [docs/README.md#release-history](docs/README.md#release-history)
328
340
  - Archived prerelease: [docs/releases/v0.1.0-beta.0.md](docs/releases/v0.1.0-beta.0.md)
329
341
 
330
342
  ## License
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAC;AAyR5D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MA2/H/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,KAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,kBAAkB,CAAC;AAwR5D;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,iBAAiB,EAAE,MA+hI/B,CAAC;AAEF,eAAO,MAAM,gBAAgB,KAAoB,CAAC;AAElD,eAAe,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@
25
25
  import { tool } from "@codex-ai/plugin/tool";
26
26
  import { AccountManager, extractAccountEmail, extractAccountId, formatAccountLabel, formatCooldown, formatWaitTime, getAccountIdCandidates, isCodexCliSyncEnabled, lookupCodexCliTokensByEmail, parseRateLimitReason, resolveRequestAccountId, resolveRuntimeRequestIdentity, sanitizeEmail, selectBestAccountCandidate, shouldUpdateAccountIdFromToken, } from "./lib/accounts.js";
27
27
  import { createAuthorizationFlow, exchangeAuthorizationCode, parseAuthorizationInput, redactOAuthUrlForLog, REDIRECT_URI, } from "./lib/auth/auth.js";
28
- import { isBrowserLaunchSuppressed, openBrowserUrl } from "./lib/auth/browser.js";
28
+ import { isBrowserLaunchSuppressed, openBrowserUrl, } from "./lib/auth/browser.js";
29
29
  import { startLocalOAuthServer } from "./lib/auth/server.js";
30
30
  import { checkAndNotify } from "./lib/auto-update-checker.js";
31
31
  import { CapabilityPolicyStore } from "./lib/capability-policy.js";
@@ -74,7 +74,7 @@ import { buildLoginMenuAccounts } from "./lib/runtime/login-menu-accounts.js";
74
74
  import { ensureLiveAccountSyncEntry } from "./lib/runtime/live-sync-entry.js";
75
75
  import { applyLoaderRuntimeSetup } from "./lib/runtime/loader-setup.js";
76
76
  import { buildManualOAuthFlow } from "./lib/runtime/manual-oauth-flow.js";
77
- import { applyPreemptiveQuotaSettingsFromConfig, } from "./lib/runtime/quota-settings.js";
77
+ import { applyPreemptiveQuotaSettingsFromConfig } from "./lib/runtime/quota-settings.js";
78
78
  import { ensureLiveAccountSyncState, ensureRefreshGuardianState, ensureSessionAffinityState, } from "./lib/runtime/runtime-services.js";
79
79
  import { mutateRuntimeObservabilitySnapshot } from "./lib/runtime/runtime-observability.js";
80
80
  import { applyAccountStorageScopeFromConfig } from "./lib/runtime/storage-scope.js";
@@ -88,7 +88,7 @@ import { clearAccounts, clearFlaggedAccounts, exportAccounts, findMatchingAccoun
88
88
  import { buildTableHeader, buildTableRow, } from "./lib/table-formatter.js";
89
89
  import { createHashlineEditTool, createHashlineReadTool, } from "./lib/tools/hashline-tools.js";
90
90
  import { formatUiBadge, formatUiHeader, formatUiItem, formatUiKeyValue, formatUiSection, paintUiText, } from "./lib/ui/format.js";
91
- import { setUiRuntimeOptions, } from "./lib/ui/runtime.js";
91
+ import { setUiRuntimeOptions } from "./lib/ui/runtime.js";
92
92
  /**
93
93
  * OpenAI Codex OAuth authentication plugin for Codex CLI host runtime
94
94
  *
@@ -191,13 +191,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
191
191
  const burstRemaining = getServerBurstCooldownRemaining(now);
192
192
  snapshot.currentRequestId = requestId;
193
193
  snapshot.poolExhaustionCooldownUntil =
194
- poolRemaining > 0
195
- ? now + poolRemaining
196
- : null;
194
+ poolRemaining > 0 ? now + poolRemaining : null;
197
195
  snapshot.serverBurstCooldownUntil =
198
- burstRemaining > 0
199
- ? now + burstRemaining
200
- : null;
196
+ burstRemaining > 0 ? now + burstRemaining : null;
201
197
  snapshot.responsesRequests = runtimeMetrics.responsesRequests;
202
198
  snapshot.authRefreshRequests = runtimeMetrics.authRefreshRequests;
203
199
  snapshot.diagnosticProbeRequests = runtimeMetrics.diagnosticProbeRequests;
@@ -300,8 +296,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
300
296
  });
301
297
  refreshGuardian = next.refreshGuardian;
302
298
  refreshGuardianConfigKey = next.refreshGuardianConfigKey;
303
- refreshGuardianCleanupRegistered =
304
- next.refreshGuardianCleanupRegistered;
299
+ refreshGuardianCleanupRegistered = next.refreshGuardianCleanupRegistered;
305
300
  };
306
301
  const ensureSessionAffinity = (pluginConfig) => {
307
302
  const next = ensureSessionAffinityState({
@@ -593,7 +588,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
593
588
  error: {
594
589
  message: `The account pool is cooling down after recent rate-limit exhaustion. Try again in ${formatWaitTime(poolCooldownRemainingMs)} or inspect \`codex auth status\`.`,
595
590
  },
596
- }), { status: 429, headers: { "content-type": "application/json; charset=utf-8" } });
591
+ }), {
592
+ status: 429,
593
+ headers: {
594
+ "content-type": "application/json; charset=utf-8",
595
+ },
596
+ });
597
597
  }
598
598
  const serverBurstCooldownRemainingMs = getServerBurstCooldownRemaining();
599
599
  if (serverBurstCooldownRemainingMs > 0) {
@@ -605,7 +605,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
605
605
  error: {
606
606
  message: `Multiple accounts recently failed with upstream server errors. Try again in ${formatWaitTime(serverBurstCooldownRemainingMs)} or inspect \`codex auth report --json\`.`,
607
607
  },
608
- }), { status: 503, headers: { "content-type": "application/json; charset=utf-8" } });
608
+ }), {
609
+ status: 503,
610
+ headers: {
611
+ "content-type": "application/json; charset=utf-8",
612
+ },
613
+ });
609
614
  }
610
615
  const maxOutboundRequestAttempts = computeOutboundRequestAttemptBudget({
611
616
  accountCount: accountManager.getAccountCount(),
@@ -628,8 +633,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
628
633
  const tryConsumeOutboundRequestAttempt = (reason, accountIndex) => {
629
634
  if (outboundRequestAttemptsRemaining <= 0) {
630
635
  runtimeMetrics.requestAttemptBudgetExhaustions += 1;
631
- runtimeMetrics.lastError =
632
- `Request attempt budget exhausted after ${maxOutboundRequestAttempts} outbound request(s)`;
636
+ runtimeMetrics.lastError = `Request attempt budget exhausted after ${maxOutboundRequestAttempts} outbound request(s)`;
633
637
  logWarn("Request attempt budget exhausted.", {
634
638
  requestTraceId,
635
639
  reason,
@@ -730,7 +734,8 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
730
734
  if (retryableRefreshPolicy.recordFailure) {
731
735
  accountManager.recordFailure(account, modelFamily, model);
732
736
  }
733
- if (typeof retryableRefreshPolicy.cooldownMs === "number" &&
737
+ if (typeof retryableRefreshPolicy.cooldownMs ===
738
+ "number" &&
734
739
  retryableRefreshPolicy.cooldownReason) {
735
740
  accountManager.markAccountCoolingDown(account, retryableRefreshPolicy.cooldownMs, retryableRefreshPolicy.cooldownReason);
736
741
  accountManager.saveToDiskDebounced();
@@ -1224,10 +1229,18 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1224
1229
  preemptiveQuotaScheduler.markRateLimited(quotaScheduleKey, cooldownMs);
1225
1230
  const waitLabel = formatWaitTime(cooldownMs);
1226
1231
  const shortRetryThresholdMs = getConfiguredRateLimitShortRetryThresholdMs();
1227
- if (cooldownMs <=
1228
- shortRetryThresholdMs &&
1232
+ if (cooldownMs <= shortRetryThresholdMs &&
1229
1233
  shortRateLimitRetryCount < MAX_SHORT_RETRY_ATTEMPTS) {
1230
1234
  shortRateLimitRetryCount += 1;
1235
+ // Mark the account unavailable BEFORE we sleep so
1236
+ // concurrent in-flight requests do not select the
1237
+ // same freshly rate-limited account during the retry
1238
+ // window. Without this, the fetch loop amplifies the
1239
+ // throttling pressure instead of spreading load.
1240
+ // The current request will continue after the sleep
1241
+ // regardless of the cooldown mark (AUDIT-H3 / D-07).
1242
+ accountManager.markRateLimitedWithReason(account, cooldownMs, modelFamily, parseRateLimitReason(rateLimit?.code), model);
1243
+ accountManager.recordRateLimit(account, modelFamily, model);
1231
1244
  if (accountManager.shouldShowAccountToast(account.index, rateLimitToastDebounceMs)) {
1232
1245
  await showRuntimeToast(client, `Rate limited. Retrying in ${waitLabel} (attempt ${shortRateLimitRetryCount})...`, "warning", { duration: toastDurationMs });
1233
1246
  accountManager.markToastShown(account.index);
@@ -1382,7 +1395,7 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1382
1395
  preemptiveQuotaScheduler.update(`${fallbackEntitlementAccountKey}:${model ?? modelFamily}`, fallbackSnapshot);
1383
1396
  }
1384
1397
  if (!fallbackResponse.ok) {
1385
- const { response: handledFallbackResponse, rateLimit: fallbackRateLimit } = await handleErrorResponse(fallbackResponse);
1398
+ const { response: handledFallbackResponse, rateLimit: fallbackRateLimit, } = await handleErrorResponse(fallbackResponse);
1386
1399
  try {
1387
1400
  await fallbackResponse.body?.cancel();
1388
1401
  }
@@ -1391,7 +1404,9 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1391
1404
  }
1392
1405
  if (handledFallbackResponse.status === 429) {
1393
1406
  const retryAfterMs = fallbackRateLimit?.retryAfterMs ?? 60_000;
1394
- const fallbackQuotaKey = model ? `${modelFamily}:${model}` : modelFamily;
1407
+ const fallbackQuotaKey = model
1408
+ ? `${modelFamily}:${model}`
1409
+ : modelFamily;
1395
1410
  const { delayMs } = getRateLimitBackoff(fallbackAccount.index, fallbackQuotaKey, retryAfterMs);
1396
1411
  const cooldownMs = Math.max(delayMs, retryAfterMs);
1397
1412
  preemptiveQuotaScheduler.markRateLimited(`${fallbackEntitlementAccountKey}:${model ?? modelFamily}`, cooldownMs);
@@ -1502,7 +1517,12 @@ export const OpenAIOAuthPlugin = async ({ client }) => {
1502
1517
  error: {
1503
1518
  message: `Upstream server failures were observed across multiple accounts. Pausing retries for ${formatWaitTime(serverBurstCooldownUntil - Date.now())}. Check \`codex auth report --json\` for runtime metrics.`,
1504
1519
  },
1505
- }), { status: 503, headers: { "content-type": "application/json; charset=utf-8" } });
1520
+ }), {
1521
+ status: 503,
1522
+ headers: {
1523
+ "content-type": "application/json; charset=utf-8",
1524
+ },
1525
+ });
1506
1526
  }
1507
1527
  }
1508
1528
  logWarn(`Empty response after ${emptyResponseMaxRetries} retries. Returning as-is.`);