Skip to content

[OpenAI/Codex] K12 5-hour quota is mislabeled as weekly and the real weekly window is discarded #1791

Closed

Description

@jojodat

Client or integration

OpenCodex dashboard

Area

Authentication and account pool

Summary

The WHAM response for plan_type: "k12" contains two simultaneous limiting windows:

  • primary: 18,000 seconds (rolling 5 hours)
  • secondary: 604,800 seconds (7 days)

OpenCodex stores the primary 5-hour value in weeklyPercent / weeklyResetAt and discards the actual weekly secondary window. Consequently, the dashboard labels the 5-hour value and reset as Week, while the real weekly usage is neither shown nor considered independently for routing.

At the same observation point, official Codex Analytics showed 99% remaining for the 5-hour limit and 100% remaining for the weekly limit. OpenCodex showed/stored weekly 1%. Converting remaining to used makes the values match exactly, proving the primary window was mislabeled rather than merely stale.

Reproduction

  1. Add a ChatGPT Codex pool account whose WHAM plan is k12.
  2. Force a quota refresh with ocx account refresh openai or Refresh quotas.
  3. Observe that OpenCodex reports only weekly 1% and uses the primary window's reset.
  4. Compare with the sanitized WHAM structure below: primary is 5 hours at 1% used; secondary is 7 days at 0% used.
  5. Compare with official Codex Analytics: 5-hour limit 99% remaining; weekly limit 100% remaining.

The separate K12 request/reauth regression is tracked in #1789. Quota refresh succeeds with HTTP 200, so this parser defect reproduces independently.

Version

2.20.0

Operating system

macOS (arm64; exact OS version omitted because the defect is not platform-specific)

Provider and model

OpenAI / ChatGPT Codex workspace login (plan_type=k12); WHAM usage endpoint

Logs or error output

Sanitized WHAM structure. The real absolute reset timestamps were replaced with synthetic integers; only their numeric type and primary/secondary association matter to this bug:

{
  "plan_type": "k12",
  "rate_limit": {
    "primary_window": {
      "used_percent": 1,
      "reset_at": 2000000000,
      "limit_window_seconds": 18000
    },
    "secondary_window": {
      "used_percent": 0,
      "reset_at": 2000586800,
      "limit_window_seconds": 604800
    },
    "tertiary_window": null
  },
  "rate_limit_reset_credits": {
    "available_count": 0
  }
}

Current OpenCodex projection of the same structure:

{
  "weeklyPercent": 1,
  "weeklyResetAt": 2000000000,
  "resetCredits": 0
}

Implementation clues in v2.20.0:

  • StoredAccountQuota in src/codex/quota.ts has weekly/monthly fields but no 5-hour fields.
  • parseUsageQuota() assigns a non-monthly primary window to weeklyPercent and only uses the secondary window as a fallback.
  • Codex routing/exhaustion evaluates weekly/monthly values, so discarding the true weekly window can affect account eligibility, not just the label.

Expected:

  • Classify windows by limit_window_seconds (18,000 = 5h; 604,800 = 7d).
  • Preserve and display both windows with their own reset values.
  • Treat the account as exhausted/unavailable when either governing window is exhausted.
  • Add parser, cache, dashboard, and routing tests for a K12 two-window payload.

Screenshots and supporting files

Related K12 authentication/workspace issue: #1789

Redacted configuration

{
  "codexAccounts": [
    {
      "plan": "k12",
      "isMain": false
    }
  ]
}

Emails, account/workspace IDs, bearer and refresh tokens, request IDs, original reset timestamps, timezone, and local paths were removed.

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. coderabbitai commented on Aug 15, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #317 - fix(codex): classify monthly WHAM windows [merged]
    #390 - fix(codex): clear stale weekly quota on WHAM/header refresh (#382) [merged]
    #750 - fix(codex): persist refreshed pool account plans [merged]
    #1290 - fix(quota): prefer Grok weekly credits for xAI dashboard [merged]
    #1373 - fix(codex): tolerate malformed account plan values [merged]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. lidge-jun commented on Aug 16, 2026

    @lidge-jun
    Owner

    Partial fix on dev via #1848 (merged as 73440d4bb) — keeping this open for the rest.

    What landed. The parser folded the primary window into weeklyPercent whenever it was not explicitly monthly, so on a plan sending a 5-hour primary plus a 7-day secondary the 5-hour bar was reported AS the weekly quota and the real weekly reading was discarded. The primary is now skipped for the weekly slot when it declares a sub-day duration, letting the 7-day secondary land there instead.

    A window with no declared duration is deliberately unchanged: older WHAM payloads omit limit_window_seconds, and inferring a duration there would reclassify every legacy account. So the discriminator is "declares a sub-day duration", not "is not seven days".

    What is still open. Your report also asks that both windows be preserved and displayed with independent reset times, and that either governing window reaching exhaustion count as exhaustion. That needs the storage to stop being two named slots (weeklyPercent/monthlyPercent plus their reset fields) and become a generic window array carrying each window's own duration and provenance.

    That is a genuine field-chain migration, not a type edit: those fields have roughly 158 references across quota, routing, capacity projection, the account API, CLI DTOs and 15 test files, and the current monthlyIsPrimaryWindow flag encodes governing-vs-supplementary provenance that a duration-only array would silently drop — risking false cooldown recovery and letting a supplementary tertiary window take an account out of rotation.

    The design for it, including the v1→v2 hydration and the plural-governing semantics your two-window case requires, is written up at devlog/_plan/260816_wave34_closeout/050_1791_quota_windows.md. Landing it as its own change rather than folding it into a mixed PR.

  3. lidge-jun commented on Aug 16, 2026

    @lidge-jun
    Owner
  4. jojodat commented on Aug 17, 2026

    @jojodat
    Author

    Please reopen this issue.

    OpenCodex 2.24.1 still drops the short-window K12 quota after parsing.

    The raw authenticated WHAM payload contains:

    • Primary window: 18000s, 0% used
    • Secondary window: 604800s, 1% used

    However, the OpenCodex refresh JSON reports weeklyPercent = 1 while shortPercent = null, shortWindowSeconds = null, and no short-window reset.

    In the installed 2.24.1 code, parseUsageQuota correctly populates shortPercent, shortResetAt, and shortWindowSeconds. The loss occurs in setAccountQuotaFromParsed, which copies only weekly/monthly/reset-credit fields and discards all short* fields. computeCodexUsageScore also considers only weeklyPercent and monthlyPercent, ignoring shortPercent.

    This matches the P1 review findings in PR #1863, so the merged fix is not end-to-end complete.

    Please preserve and consume the short-window fields across the full path:

    WHAM payload → parseUsageQuota → account cache → API/dashboard → routing score
    

    Regression coverage should verify that:

    1. Parsed shortPercent, shortResetAt, and shortWindowSeconds survive cache assignment.
    2. The API/dashboard exposes the short-window usage and window duration.
    3. Routing incorporates short-window pressure alongside longer-window usage.
    4. Missing short-window data remains distinguishable from a genuine 0% value.

    No additional private data should be necessary. If a diagnostic build is needed, I can offer one anonymized K12 test run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansbugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions