[Bug] 2.77.0: "Use credits" never applies after weekly limit — credits.allowed copied from rate_limit.allowed #6571
Description
Client or integration
Codex App
Area
Authentication and account pool
Summary
Regression in 2.77.0 (introduced by 6ef255f "fix(codex): require consent for spendable-credit headroom (carry #6466)").
After the main Codex account hits its weekly included-usage limit, enabling "Use credits" for the main account in Codex Auth has no effect. Every request is rejected locally with HTTP 429 CodexMainAccountCreditsOffError even though upstream reports spendable credits and the model as available.
Root cause: credits.allowed is populated from data.rate_limit.allowed, and hasSpendableCodexCredits() returns false whenever credits.allowed === false.
// src/codex/quota.ts:929 (v2.77.0, credits snapshot)
...typeof data.rate_limit?.allowed === "boolean" ? { allowed: data.rate_limit.allowed } : {},
// src/codex/quota-types.ts:45 (v2.77.0, hasSpendableCodexCredits)
if (age < 0 || age > CODEX_CREDITS_FRESHNESS_MS || credits.allowed === false || ...) return false;rate_limit.allowed describes the included plan window only. It is always false once limit_reached: true, which is exactly the situation where credits are supposed to take over. So hasSpendableCodexCredits() can never return true at the moment it matters, mainCreditsHoldResetAt() always returns a reset time, and assertMainAccountPolicy() throws before the request reaches upstream (~10 ms 429s).
Expected: with creditCodexAccountIds containing __main__, and upstream reporting credits.has_credits: true, balance > 0, overage_limit_reached: false, spend_control.reached: false and model_usage[model].available: true, requests should be forwarded and billed to credits (as they were on 2.76.0).
Suggested fix: do not derive credits.allowed from rate_limit.allowed. If a refusal signal is needed, use model_usage[<model>].available / spend_control.reached / credits.overage_limit_reached instead.
Reproduction
- Have the main ChatGPT (Pro) account at 100% of its weekly Codex limit, with a positive ChatGPT credits balance.
- Codex Auth → main account → enable "Use credits" (
creditCodexAccountIdsnow contains__main__). - Send any request from Codex App through the proxy (model
gpt-6-astra). - Observe: 429
rate_limit_exceeded,failureStage: "headers-only",failureCause: "rate-limit", with the message below. Toggling the setting or waiting for a quota refresh doesn't help.
Timeline from dpkg.log / usage.jsonl on my machine:
12:06:03upgradeopen-codex 2.76.0 → 2.77.0- before that: main-account
openairequests returned 200 12:06:18firstCodexMainAccountCreditsOffError, and every main-account request since then is a 429
Version
2.77.0 (deb open-codex). Last known good: 2.76.0
Operating system
Ubuntu 26.04.1 LTS (x86_64)
Provider and model
openai (native Codex / ChatGPT main account), gpt-6-astra
Logs or error output
Proxy usage record (trimmed):
{
"provider": "openai",
"model": "gpt-6-astra",
"admissionKind": "loopback",
"status": 429,
"durationMs": 10,
"errorCode": "rate_limit_exceeded",
"upstreamError": "Codex main account reached its usage limit, and spending ChatGPT credits is off or no fresh spendable balance is available. Choose another account, wait for the limit to reset, or allow the main account under \"Use credits\" in Codex Auth.",
"routeDecision": { "routeKind": "native", "selected": { "provider": "openai", "model": "gpt-6-astra", "reason": "native-family" } },
"failureStage": "headers-only",
"failureCause": "rate-limit"
}Upstream GET /backend-api/wham/usage at the same time (identifiers removed, balance rounded):
{
"plan_type": "pro",
"rate_limit": { "allowed": false, "limit_reached": true,
"primary_window": { "used_percent": 100, "limit_window_seconds": 604800 } },
"model_usage": { "gpt-6-astra": { "available": true, "available_at": null, "credits_would_enable": false } },
"credits": { "has_credits": true, "unlimited": false, "overage_limit_reached": false, "balance": "~52000" },
"spend_control": { "reached": false, "individual_limit": null },
"rate_limit_reached_type": { "type": "rate_limit_reached", "details": "default" }
}Resulting cached main quota in codex-quota-cache.json:
"credits": { "hasCredits": true, "unlimited": false, "overageLimitReached": false, "allowed": false, "balance": 52015.75 },
"weeklyPercent": 100Redacted configuration
{
"codexMainAccountHardLock": false,
"creditCodexAccountIds": ["__main__", "<account-2>", "<account-3>"]
}Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
Ingwannu commentedon Oct 4, 2026
Confirmed against trusted dev
33185c2ccf7085f94357d9e56512803949b22cc5; the included-plan flag was incorrectly copied into the credit-specific veto. I opened draft #6572 ate7ee61d1cd65b52c07cc1dba34c506a6444e9254with credit-specific spending-control parsing, opt-in/balance/freshness/overage/hard-lock preservation, and parser/main-admission regressions. The old implementation fails six new/corrected parser cases; the patched parser and main suites pass 60 tests, 0 failures, 141 assertions. Structure and file-size checks pass. Typecheck timed out under CPU75%/RAM1536M and is explicitly incomplete; broader CI and independent credit-boundary review remain pending. No real account, cache, provider call or credit spending was changed. This is a proposed fix, not a merged or released fix; the issue stays open.fix(codex): honor consented credits after included quota exhaustion (#…
Ingwannu commentedon Oct 4, 2026
Fixed on dev by #6572, merged by lidge-jun at 2dd3f82. Closing the reported 2.77.0 regression: included-window rate_limit.allowed no longer supplies credits.allowed; credit-specific spend_control, opt-in consent, freshness/balance and hard-lock checks remain. The final follow-up also vetoes malformed present controls and retracts cached permission on control-only refusals.
Exact-head CI37209972954 passed for e9bcbe9. The parser/main-admission regressions and independent-review receipt are documented in #6572; earlier locally executed counts remain head-bound, not a new real-account run. This is a dev fix, not a claim that a release is published or the reporter's installed account has been exercised. Thank you @sungyongcho; attribution is retained in the landed commit.
Update from upstream 2.80.0 while preserving fork integrations (#9)