Repository navigation
feat(codex): let an account stop at its usage limit instead of spending ChatGPT credits - #6422
sungyongcho wants to merge 1 commit into
Conversation
…ng ChatGPT credits
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
⏳ DRAFT
What to do
Review readiness checklist
0/4 boxes ticked. This pull request was already a draft. Its draft status will be preserved after every issue above is resolved. |
|
@lidge-jun The hygiene gate flags
Nothing in the file changes how a credential is read, stored, refreshed or forwarded, and with the switch at its default (on) none of the new branches run. I am happy to move the main-account part into a separate PR if you would rather review the pool part first. |
Refines #6422 for #6334. An account at 100% is now switched out by default and returns after its reset; spending credits is opt-in. - Store the allow-list creditCodexAccountIds instead of the opt-out list noCreditCodexAccountIds; absence means no account spends credits, and a new account starts off. - PUT /api/codex-auth/accounts/credits also takes { all } for the global switch: true lists __main__ and every pool account, false clears it. - Dashboard: one global "Use credits" switch beside the Codex credits display switch, derived from the rows (off / mixed / on, like the quota auto-refresh control), and a count button that opens a per-account panel below the header. Cards drop their own switch and show a "Uses credits" badge on accounts allowed to spend. Co-authored-by: Sungyong Cho <dev@sungyongcho.com>
Refines #6422 for #6334. An account at 100% is now switched out by default and returns after its reset; spending credits is opt-in. - Store the allow-list creditCodexAccountIds instead of the opt-out list noCreditCodexAccountIds; absence means no account spends credits, and a new account starts off. - PUT /api/codex-auth/accounts/credits also takes { all } for the global switch: true lists __main__ and every pool account, false clears it. - Dashboard: one global "Use credits" switch beside the Codex credits display switch, derived from the rows (off / mixed / on, like the quota auto-refresh control), and a count button that opens a per-account panel below the header. Cards drop their own switch and show a "Uses credits" badge on accounts allowed to spend. Co-authored-by: Sungyong Cho <dev@sungyongcho.com>
…tGPT credits opt-in (#6444) An account at 100% of a usage window is now switched out by default and returns after its reset; spending ChatGPT credits is opt-in through creditCodexAccountIds. The Codex Auth header has one global "Use credits" switch beside the credits display, and each account's switch lives in its card's more menu. Refines #6422 and closes #6334. Co-authored-by: Sungyong Cho <dev@sungyongcho.com>
|
Thank you, @sungyongcho. This landed through #6444 (squash The maintainers made one product change on top: spending credits after the limit is now opt-in. By default, an account at 100% is switched out and returns after its reset. The setting is stored as an allow-list, |
Summary
Closes #6334.
An account that holds ChatGPT credits is not refused by upstream when one of its usage windows reaches 100%: upstream serves the request and draws the credit balance. Account selection only takes an account off on quota after a refusal, so such an account was never moved off, and with
autoSwitchThreshold: 0it kept spending credits while other accounts in the pool still had included usage. This adds one per-account switch, Use credits after limit, on every Codex account card, the main account included.isTerminalShortWindowrule. So the account comes back after its reset without anyone refreshing it.src/codex/account-credit-use.tsowns the list (noCreditCodexAccountIds) and the full-window rule. Insrc/codex/routing/selection.tsthe hold is checked wherever plan exclusion is checked: the eligible list,isCodexAccountSelectable(an active or bound account is served without passing through the eligible list), andcodexAccountBlockReason, which reports a newcredits_offreason.isCodexAccountRotationExcludedcarries plan exclusion and the hold into the two legacy keep-the-active-account fallbacks and the transient-only affinity check insrc/codex/routing.ts, so a pool whose last available account is held selects none instead of spending.routing.tsstays at its size cap: those three call sites are replaced, not added to.src/codex/auth-context.tschecks it where the main-account hard lock is checked:assertMainAccountPolicythrows a newCodexMainAccountCreditsOffError, a cooldown error mapped like the hard-lock refusal, andrequestOwnedMainPinStatestops preserving a caller's own main credential. Both read the main policy quota the hard lock reads and never the plan, because several of those callers may not open the physical auth file. With the hard lock on (the default) the main account is usually stopped at 98% first; the switch matters when the lock is off or set to 100%.showCodexCredits) is on, so the credits row and the switch appear and hide together. With the display off, an account whose switch is off shows a Credits off badge, so a held account never looks ordinary. The display setting still never changes routing.PUT /api/codex-auth/accounts/creditswith{ id, creditsAfterLimit }for pool accounts and__main__(400 for a bad id or a non-boolean, 404 for an unknown account). The account list DTO gainscreditsAfterLimiton every row.gui/src/components/AccountCreditsToggle.tsxreuses the custom-threshold row styles, so no CSS was added; strings are in all ten locales. A deleted account is forgotten from the list.deferred-verb(owner "[Feature]: per-account switch to stop using an account once it reaches its usage limit instead of spending its ChatGPT credits #6334 follow-up", named instructure/codex-account-controls.md);ocx config set noCreditCodexAccountIdscovers scripted use. Happy to add anocx accountverb if you want it in this PR.noCreditCodexAccountIdsrow in the configuration reference, the route in the management API reference, a paragraph next to the Codex credits switch in the Codex integration guide, and a section instructure/codex-account-controls.md.Observed on my pool before this change: one account went from 90% to 100% weekly while its balance went from 62,500 to 0, while the other pool accounts read 0% weekly the evening before; another account at 100% weekly kept being selected and its balance kept falling. Details are in #6334.
Verification
tests/codex-integration/codex-credits-after-limit.test.ts(15 tests): the default keeps a 100% account selected; with the switch off it is skipped byresolveandpreviewand reportscredits_off; a thread already bound to it moves off; below 100% nothing changes; an elapsed reset and a reading without a reset time do not hold; the last account held selects none; only listed ids are stored,__main__included; weekly reset in milliseconds and seconds, monthly on a 30-day plan, the 5-hour window; and the route (on, off,__main__, bad id, unknown id, non-boolean).tests/codex-integration/codex-credits-after-limit-main.test.ts(6 tests, hard lock off so only the switch can refuse): by default a full main login keeps serving; with the switch off it is refused withCodexMainAccountCreditsOffErrorcarrying the reset, also for an explicit main request; the pool moves to another account; a caller carrying the main credential moves too, with physical auth reads forbidden; an elapsed reset or 99% releases it; and selection alone leaves the held main out.isCodexAccountSelectable, the reset-time rule,assertMainAccountPolicy,requestOwnedMainPinState, and the main branch of the eligible list.gui/tests/codex-credits-after-limit-toggle.test.tsx(6 tests: main and pool cards show the switch with the credits display, on by default for rows without the field; with the display off the switch is hidden and a held account is badged; unwired pages show nothing; click and pending-write blocking) and a controller test ingui/tests/codex-account-pool-behaviour.test.tsx. Treating a missing field as off made the default tests fail; reverted.scripts/test-layout/layout.jsonandtests/fixtures/test-layout-expected.json, and the new writer intests/config/config-rebase-provenance-writers.test.ts.bun run typecheck,bun run structure:checkandbun run privacy:scan: pass.bun run build,bun run lint,bun run lint:i18npass, and the whole GUI suite the way CI runs it,cd gui && bun test --isolate tests: 2725 pass, 0 fail.npx react-doctor@0.9.11 --verbose --scope changedagainstdev: 20 files, no issues.bun run test: 35002 pass, 77 skip, 96 fail. 95 of them are in 18 files this PR does not touch, and the same files fail with the same counts at thedevtip without this PR (tests/update/update-restart-lease.test.tschecked twice there). The 96th was a 30 s timeout intests/server/server-auth.test.tswhile I was capturing screenshots in parallel; that file passes alone, 117 of 117, twice. My machine has no standalone Bun, so the suite runs through the packagedocxbinary in Bun mode; that explains the subprocess failures such asUnknown command: -e.Checklist
__main__sentinel), the default leaves routing unchanged, and the main-account check reads only the in-memory policy quota, never the physical auth file.Screenshots
Dashboard built from this branch with fictional data (a main account at 0% and two Pro pool accounts at 100% weekly):
sub2.sub2carries the Credits off badge.Review readiness checklist
This PR stays in draft until every box below is ticked. Tick all four boxes once the requirements are met:
Required local validation passed; commands, results, and any full-suite exception are documented.
I pushed my PR to a recent dev commit (at most 10 behind; a maintainer may still ask for the exact tip before merge).
I resolved all correct Codex and CodeRabbit findings.
My PR is ready for review.