Symptom
After the real ChatGPT/Codex usage window reset, the UI showed quota headroom again, but the proxy kept rejecting Claude Code requests with local account-selection failures such as:
Request rejected (429) · All configured accounts that support "gpt-5.5" are rate-limited ...
API Error: 503 Service temporarily unavailable
- gateway log:
openai_messages.account_select_failed / error: "no available accounts"
This was reproducible even though direct quota state was no longer exhausted.
Evidence from local Docker runtime
Runtime: sub2api-codex Docker compose on port 18081.
Before recovery, the account row had stale quota state in accounts.extra, including old Codex usage and model cooldowns. Manually clearing stale codex_*used_percent and quota-origin model_rate_limits made requests work again without changing the upstream account.
After the code fix and image rebuild, the live DB state is:
id=1 platform=openai status=active codex_5h_used_percent=2 codex_7d_used_percent=0
model_rate_limits={"gpt-5.6-luna":{"reason":"upstream_404_model_not_found",...}}
The quota-origin fake cooldowns for gpt-5.5, gpt-5.6-sol, and gpt-5.6-terra are gone. The non-quota gpt-5.6-luna upstream-404 cooldown is intentionally preserved.
Live probes through /v1/messages after redeploy:
gpt-5.5: HTTP 200
gpt-5.6-sol: HTTP 200
gpt-5.6-terra: HTTP 200
gpt-5.6-luna: local 503 from preserved upstream_404_model_not_found, not a quota cooldown
Root Cause
sub2api persisted OpenAI/Codex model cooldowns in accounts.extra.model_rate_limits. When OpenAI/Codex quota reset outside the proxy, stale openai_model_rate_limited entries could survive and account selection filtered the account before any upstream request had a chance to refresh the real usage headers.
The existing Codex usage snapshot update path wrote fresh codex_* usage fields, but did not clear obsolete quota-origin model_rate_limits. That made the proxy report fake 429/503 errors after quota was already available again.
Fix in fork
When a fresh Codex usage snapshot/probe shows quota headroom, clear only quota-origin OpenAI model cooldowns:
- remove
reason == openai_model_rate_limited
- remove
reason prefixed with openai_model_rate_limited:
- preserve upstream model-not-found cooldowns, image cooldowns, and unknown reasons
The recovery is wired into both:
- live gateway usage-header snapshots
- account usage/probe snapshot persistence
Tests
Added regression coverage for:
- detecting Codex quota headroom from fresh usage headers
- preserving non-quota cooldown reasons
- clearing stale quota cooldowns after a fresh non-exhausted snapshot
- not clearing quota cooldowns when a fresh snapshot still reports an exhausted window
Verification command:
go test ./internal/service -count=1
Result:
ok github.com/Wei-Shaw/sub2api/internal/service 51.057s
Docker verification
Rebuilt and redeployed the local image:
docker build -t sub2api-codex:local-token-usage .
docker compose -p sub2api-codex up -d sub2api
Container health after deploy:
sub2api-codex Up (healthy) 0.0.0.0:18081->8080/tcp
/health => {"status":"ok"}
Symptom
After the real ChatGPT/Codex usage window reset, the UI showed quota headroom again, but the proxy kept rejecting Claude Code requests with local account-selection failures such as:
Request rejected (429) · All configured accounts that support "gpt-5.5" are rate-limited ...API Error: 503 Service temporarily unavailableopenai_messages.account_select_failed/error: "no available accounts"This was reproducible even though direct quota state was no longer exhausted.
Evidence from local Docker runtime
Runtime:
sub2api-codexDocker compose on port18081.Before recovery, the account row had stale quota state in
accounts.extra, including old Codex usage and model cooldowns. Manually clearing stalecodex_*used_percentand quota-originmodel_rate_limitsmade requests work again without changing the upstream account.After the code fix and image rebuild, the live DB state is:
The quota-origin fake cooldowns for
gpt-5.5,gpt-5.6-sol, andgpt-5.6-terraare gone. The non-quotagpt-5.6-lunaupstream-404 cooldown is intentionally preserved.Live probes through
/v1/messagesafter redeploy:gpt-5.5: HTTP 200gpt-5.6-sol: HTTP 200gpt-5.6-terra: HTTP 200gpt-5.6-luna: local 503 from preservedupstream_404_model_not_found, not a quota cooldownRoot Cause
sub2apipersisted OpenAI/Codex model cooldowns inaccounts.extra.model_rate_limits. When OpenAI/Codex quota reset outside the proxy, staleopenai_model_rate_limitedentries could survive and account selection filtered the account before any upstream request had a chance to refresh the real usage headers.The existing Codex usage snapshot update path wrote fresh
codex_*usage fields, but did not clear obsolete quota-originmodel_rate_limits. That made the proxy report fake 429/503 errors after quota was already available again.Fix in fork
When a fresh Codex usage snapshot/probe shows quota headroom, clear only quota-origin OpenAI model cooldowns:
reason == openai_model_rate_limitedreasonprefixed withopenai_model_rate_limited:The recovery is wired into both:
Tests
Added regression coverage for:
Verification command:
go test ./internal/service -count=1Result:
Docker verification
Rebuilt and redeployed the local image:
docker build -t sub2api-codex:local-token-usage . docker compose -p sub2api-codex up -d sub2apiContainer health after deploy: