Files
magpie/internal/provider/testdata/codex_usage_team_spend_cap.json
forever 8005f17551 provider, codex: a ChatGPT account at 100% with credits left keeps Codex signed in (#966)
* provider, codex: a ChatGPT account at 100% with credits left keeps Codex signed in — magpie becomes its provider only while the Codex app itself holds the account

magpie made itself Codex's provider (model_provider = "magpie", #540)
whenever any of the active ChatGPT account's windows read 100%. A Pro
account at 100% on its week with credits left keeps working in the Codex
app: the backend answers every turn (about 490 200s in one day's usage log
at 100%), yet magpie moved Codex off it, and as a provider without
requires_openai_auth the app lost its ChatGPT state ("Sign in to ChatGPT
to start a durable thread", no remote control).

CodexUsedUp now reads codexHeld, which mirrors the Codex app's own
composer block (ChatGPT.app webview bundle, hP and its helpers): the
backend says the account isn't allowed (rate_limit.allowed = false), it
has no credits (has_credits / unlimited), and it isn't a workspace still
within its overage (overage_limit_reached = false, no spend cap, an
ordinary rate_limit_reached). Window percent never decides it. Where the
app goes by what /wham/usage can't show (a statsig gate, the gpt-reserve
fallback, Team's reserve experiment that ignores overage), it counts as
held: a wrong move costs ChatGPT extras, a missed one every turn. A
reading kept through a failed read (keepLast) stops saying held once its
used-up window has started again, as its usage does. NextLogin's account
rotation and its thresholds are unchanged.

A user stuck on the old move (codex.out = 1) is put back beside the
sign-in by the next Sync, as #540 already does once the account has room.

Verification:
- TestCodexUsedUpIsWhatTheAppHolds (real /wham/usage replies, ids
  replaced) fails on the old trigger: 7 subcases, "CodexUsedUp = true,
  want false". Each workspace guard (spend cap, reason, overage, plan
  list, Team reserve) was mutated and fails its own subcase.
- TestCodexHeldKeptOnlyTillTheReset fails without the quota_last change.
- The real Pro account (7-day window 100%, ~28k credits, allowed false):
  CodexUsedUp true before, false after.
- go vet ./...; go build (darwin), GOOS=linux -tags nogui, GOOS=windows;
  go test -tags nogui ./... under a sandbox HOME; -race -count=20 on the
  new and related Codex tests.
- Not run: a live Codex app turn on an account truly held (none at hand),
  Windows/WSL runtime, GUI suites (no GUI change).

* provider: a Codex account at a spend cap or past its overage is held, credits or not; limit_reached counts as not allowed

codexHeld let credits through before the spend cap, so a Team seat at its
cap (spend_control.reached, allowed false) with has_credits read not held
and magpie left Codex on an account the backend refuses. The app's
readiness check (26.930.61225, app-primary) returns limited on
spend_control.reached or credits.overage_limit_reached before it looks at
credits, and treats rate_limit.limit_reached like allowed false; held now
reads the same. A spend cap with the rate limit still allowed (a
workspace's included usage) stays not held.
2026-10-06 14:50:39 +08:00

77 lines
1.9 KiB
JSON

{
"user_id": "user-1",
"account_id": "acct-1",
"email": "me@example.com",
"plan_type": "team",
"rate_limit": {
"allowed": false,
"limit_reached": true,
"primary_window": {
"used_percent": 100,
"limit_window_seconds": 18000,
"reset_after_seconds": 9452,
"reset_at": 1791264745
},
"secondary_window": {
"used_percent": 63,
"limit_window_seconds": 604800,
"reset_after_seconds": 360157,
"reset_at": 1791615450
}
},
"code_review_rate_limit": null,
"additional_rate_limits": null,
"model_usage": {
"gpt-6-astra": {
"available": false,
"available_at": "2026-10-06T05:32:26.047406Z",
"credits_would_enable": false
}
},
"credits": {
"has_credits": true,
"unlimited": false,
"overage_limit_reached": false,
"balance": null,
"approx_local_messages": null,
"approx_cloud_messages": null
},
"spend_control": {
"reached": true,
"individual_limit": {
"source": "account_user_spend_controls",
"unit": "credit",
"limit": "0",
"used": "0.0",
"remaining": "0.0",
"used_percent": 100,
"remaining_percent": 0,
"reset_after_seconds": 2235907,
"reset_at": 1793491201
}
},
"rate_limit_reached_type": {
"type": "workspace_owner_usage_limit_reached",
"details": null
},
"rate_limit_upsell": {
"banner_type": "workspace_owner_usage_limit_reached",
"title": "Usage limit reached",
"description": "You've reached your usage limit. Your limit will reset {time}, or you can increase your limits",
"ctas": [
{
"action": "increase_spend_cap",
"label": "Increase Usage Limit"
}
],
"reset_at": 1793491201,
"referral": null,
"request_url": null
},
"promo": null,
"rate_limit_reset_credits": {
"available_count": 2,
"applicable_available_count": 2
}
}