Skip to content

feat(codex): classify reset-eligible quota rejection - #866

Open
luvs01 wants to merge 2 commits into
lidge-jun:devfrom
luvs01:feat/657-reset-eligible-exhaustion
Open

feat(codex): classify reset-eligible quota rejection#866
luvs01 wants to merge 2 commits into
lidge-jun:devfrom
luvs01:feat/657-reset-eligible-exhaustion

Conversation

@luvs01

@luvs01 luvs01 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Safety boundary

  • reads only a bounded clone of the upstream error response
  • malformed, oversized, consumed, or cancelled bodies fail closed and never become reset-credit eligible
  • message text cannot authorize reset-credit eligibility
  • HTTP status remains authoritative: a quota-looking body under 401/403/5xx is not reset eligible
  • the original response remains available for passthrough when no alternate account exists
  • no raw body, account identifier, credential, or token is logged or persisted

Explicit non-goals

This PR deliberately does not implement:

  • automatic reset-credit redemption
  • reset-credit balance or consume calls
  • natural-reset waiting
  • idempotency or single-flight redemption
  • cancellation-bound replay
  • recovery priority configuration
  • GUI or documentation controls

Those remain separate follow-up slices under the maintainer direction in #657.

Compatibility

The existing #584 behavior is preserved: pre-stream 429/402 still permits one bounded alternate-account attempt even when the rejection is generic or unverified. This PR only establishes the semantic boundary future irreversible recovery must require.

Verification

  • Bun 1.4.0-canary.1: 69 focused classifier and server-auth tests passed
  • bundled Bun 1.3.14: the same 69 tests passed
  • TypeScript typecheck passed
  • privacy scan passed on both Bun runtimes

Refs #657

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of Codex quota and billing responses before streaming begins.
    • Retry behavior now distinguishes verified quota exhaustion from generic rate limits, authentication issues, permissions, and temporary server errors.
    • Only explicitly confirmed exhaustion responses qualify for reset-credit handling, reducing incorrect retry attempts.
    • Added safer handling for malformed, incomplete, oversized, or interrupted error responses.
    • Preserved response details while evaluating eligibility, including when requests are cancelled.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 28238cdf-f7f6-4c6d-b5f3-a4696972dcd3

📥 Commits

Reviewing files that changed from the base of the PR and between 08949c0 and aef44bd.

📒 Files selected for processing (3)
  • src/codex/quota-rejection.ts
  • src/server/responses/core.ts
  • tests/codex-quota-rejection.test.ts

📝 Walkthrough

Walkthrough

Codex pre-stream responses now receive strict structured rejection classification. Pool-account retry handling uses the asynchronous result and abort signal. Tests cover quota schemas, status categories, malformed bodies, cancellation, and response preservation.

Changes

Codex quota retry handling

Layer / File(s) Summary
Pre-stream rejection classifier
src/codex/quota-rejection.ts
Adds rejection contracts and asynchronous classification for authentication, permission, server, rate-limit, billing, quota, and other responses. Only exact matching exhaustion codes on HTTP 429 or 402 receive reset-credit eligibility.
Pool-account retry integration and validation
src/server/responses/core.ts, tests/codex-quota-rejection.test.ts
Pool-account retry handling awaits classification, passes the abort signal, and retries only alternate-account-eligible responses. Tests cover strict schemas, ambiguous payloads, malformed bodies, cancellation, and status handling.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • lidge-jun/opencodex#565: Both changes modify Codex quota-exhaustion handling, but this change classifies pre-stream responses while #565 pauses accounts after confirmed exhaustion.
  • lidge-jun/opencodex#599: This change extends the Codex pool retry logic with structured quota-rejection classification.
  • lidge-jun/opencodex#671: Both changes modify Codex pool retry behavior in src/server/responses/core.ts; this change refines alternate-account eligibility through rejection classification.

Suggested reviewers: ingwannu, lidge-jun, wibias

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: classifying Codex quota rejections eligible for reset handling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

luvs01 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@lidge-jun

Copy link
Copy Markdown
Owner

Pre-merge review result: not merge-ready yet. CI is green and the bounded body handling, privacy posture, and Bun compatibility all check out — but the classifier does not hold the fail-closed boundary the PR description promises:

  1. src/codex/quota-rejection.ts:59 normalizes with trim().toLowerCase(), so near-miss values like " Usage_Limit_Exceeded " or "USAGE_LIMIT_EXCEEDED" classify as reset-credit eligible. Eligibility must be exact, case-sensitive, and allowlisted.
  2. src/codex/quota-rejection.ts:51-63 accepts both root and nested error shapes and both code and type fields, so ambiguous or contradictory payloads (e.g. {error: "opaque", code: "usage_limit_exceeded"}, {error: {code: "unknown", type: "insufficient_quota"}}) are treated as eligible. Unknown or conflicting shapes must fail closed.
  3. tests/codex-quota-rejection.test.ts has no negative coverage for casing/whitespace near-misses or ambiguous/conflicting shapes, so the current suite passes while the boundary leaks.

Please: exact-match allowlisted values within explicitly supported response schemas, reject on conflict, and add the negative tests. This is the semantic foundation for the #657 recovery family, so it is worth getting airtight — happy to re-review quickly once updated.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/server/responses/core.ts`:
- Around line 250-255: Update shouldRetryCodexPoolAccountQuota to be synchronous
and return true only when response.status is 402 or 429, avoiding
classifyCodexPreStreamRejection and any response-body read. Remove the
now-unused classifier import while preserving the existing boolean retry
contract.

In `@tests/codex-quota-rejection.test.ts`:
- Around line 12-13: Extend the test suite in “Codex pre-stream quota rejection
classification” with handleResponses integration coverage for Codex pool
retries. Exercise the reset-eligible 429 and 402 paths in handleResponses,
asserting each retries on an alternate account, and add a non-retryable-status
case asserting the original status and response body are preserved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3d172e90-de04-4759-a458-eb24d70ac61a

📥 Commits

Reviewing files that changed from the base of the PR and between aae9426 and 08949c0.

📒 Files selected for processing (3)
  • src/codex/quota-rejection.ts
  • src/server/responses/core.ts
  • tests/codex-quota-rejection.test.ts

Comment on lines +250 to +255
async function shouldRetryCodexPoolAccountQuota(
response: Response,
signal?: AbortSignal,
): Promise<boolean> {
const rejection = await classifyCodexPreStreamRejection(response, { signal });
return rejection.alternateRetryEligible;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the bounded-body timeout magnitude and confirm no other unawaited call site.
rg -n 'BOUNDED_BODY_TIMEOUT_MS\s*=' src/lib/bounded-body.ts
rg -nP '\bshouldRetryCodexPoolAccountQuota\s*\(' src/server/responses/core.ts

Repository: lidge-jun/opencodex

Length of output: 361


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- quota-rejection symbols ---'
ast-grep outline src/codex/quota-rejection.ts
printf '%s\n' '--- quota-rejection implementation ---'
sed -n '1,190p' src/codex/quota-rejection.ts
printf '%s\n' '--- response helper and call site ---'
sed -n '220,275p' src/server/responses/core.ts
sed -n '1585,1665p' src/server/responses/core.ts
printf '%s\n' '--- bounded body helper ---'
ast-grep outline src/lib/bounded-body.ts
sed -n '1,180p' src/lib/bounded-body.ts
printf '%s\n' '--- related call sites and tests ---'
rg -n 'classifyCodexPreStreamRejection|alternateRetryEligible|resetCreditEligible|semanticCode|shouldRetryCodexPoolAccountQuota' src test tests 2>/dev/null || true

Repository: lidge-jun/opencodex

Length of output: 22739


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
import re

quota = Path("src/codex/quota-rejection.ts").read_text()
core = Path("src/server/responses/core.ts").read_text()
bounded = Path("src/lib/bounded-body.ts").read_text()

assert "const semanticCode = await resetEligibleCodeFromResponse(response, options.signal);" in quota
assert re.search(
    r'if \(semanticCode\).*?alternateRetryEligible: true',
    quota,
    re.S,
)
assert re.search(
    r'status === 429 \? "generic-rate-limit" : "unverified-billing-or-quota".*?'
    r'\{ alternateRetryEligible: true \}',
    quota,
    re.S,
)

def classifier_retry(status: int) -> bool:
    return status in (402, 429)

def proposed_retry(status: int) -> bool:
    return status == 429 or status == 402

statuses = [200, 400, 401, 402, 403, 429, 500, 503]
mismatches = [(s, classifier_retry(s), proposed_retry(s))
              for s in statuses if classifier_retry(s) != proposed_retry(s)]
assert not mismatches, mismatches

assert "response.clone()" in quota
assert "BOUNDED_BODY_TIMEOUT_MS = 5_000" in bounded
assert len(re.findall(r'\bshouldRetryCodexPoolAccountQuota\s*\(', core)) == 2
print("status-only decision matches classifier for:", statuses)
print("classifier reads response.clone() for 402/429 only")
print("bounded body default timeout: 5000 ms")
print("helper declaration plus one call site found")
PY

printf '%s\n' '--- import usage ---'
rg -n -F 'classifyCodexPreStreamRejection' src/server/responses/core.ts src
printf '%s\n' '--- bounded body cleanup ---'
sed -n '120,230p' src/lib/bounded-body.ts

Repository: lidge-jun/opencodex

Length of output: 3216


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path
import re

quota = Path("src/codex/quota-rejection.ts").read_text()
core = Path("src/server/responses/core.ts").read_text()
bounded = Path("src/lib/bounded-body.ts").read_text()

assert "const semanticCode = await resetEligibleCodeFromResponse(response, options.signal);" in quota
assert re.search(r'if \(semanticCode\).*?alternateRetryEligible: true', quota, re.S)
assert re.search(
    r'status === 429 \? "generic-rate-limit" : "unverified-billing-or-quota".*?'
    r'\{ alternateRetryEligible: true \}',
    quota,
    re.S,
)

def classifier_retry(status: int) -> bool:
    return status in (402, 429)

def proposed_retry(status: int) -> bool:
    return status == 429 or status == 402

statuses = [200, 400, 401, 402, 403, 429, 500, 503]
mismatches = [(s, classifier_retry(s), proposed_retry(s))
              for s in statuses if classifier_retry(s) != proposed_retry(s)]
assert not mismatches, mismatches

assert "response.clone()" in quota
assert "BOUNDED_BODY_TIMEOUT_MS = 5_000" in bounded
assert len(re.findall(r'\bshouldRetryCodexPoolAccountQuota\s*\(', core)) == 2
print("status-only decision matches classifier for:", statuses)
print("classifier reads response.clone() for 402/429 only")
print("bounded body default timeout: 5000 ms")
print("helper declaration plus one call site found")
PY

printf '%s\n' '--- import usage ---'
rg -n -F 'classifyCodexPreStreamRejection' src/server/responses/core.ts src
printf '%s\n' '--- bounded body cleanup ---'
sed -n '120,230p' src/lib/bounded-body.ts

Repository: lidge-jun/opencodex

Length of output: 3216


Remove the body read from the pool-account retry decision.

classifyCodexPreStreamRejection reads response.clone() for 402 and 429, but both branches always set alternateRetryEligible: true. This can delay failover by up to 5,000 ms. Make shouldRetryCodexPoolAccountQuota synchronous and return response.status === 402 || response.status === 429. Remove the unused classifier import.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/responses/core.ts` around lines 250 - 255, Update
shouldRetryCodexPoolAccountQuota to be synchronous and return true only when
response.status is 402 or 429, avoiding classifyCodexPreStreamRejection and any
response-body read. Remove the now-unused classifier import while preserving the
existing boolean retry contract.

Comment on lines +12 to +13
describe("Codex pre-stream quota rejection classification", () => {
test.each([

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether an existing test already covers the core.ts pool-retry integration.
rg -ln 'shouldRetryCodexPoolAccountQuota|retryCodexPoolOnAlternateAccount' tests/

Repository: lidge-jun/opencodex

Length of output: 157


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- relevant files ---'
git ls-files 'src/server/responses/core.ts' 'tests/*codex*' 'tests/*quota*'

printf '%s\n' '--- changed-file summary ---'
git diff --stat

printf '%s\n' '--- core.ts symbol references ---'
rg -n -C 8 'shouldRetryCodexPoolAccountQuota|retryCodexPoolOnAlternateAccount|classifyCodexPreStreamRejection' src/server/responses/core.ts

printf '%s\n' '--- quota test contents ---'
sed -n '1,240p' tests/codex-quota-rejection.test.ts

printf '%s\n' '--- related test references ---'
rg -n -C 4 'Codex|quota|pool|alternate account|pre-stream' tests/ -g '*.test.ts'

Repository: lidge-jun/opencodex

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- changed files ---'
git diff --name-only

printf '%s\n' '--- core.ts diff ---'
git diff -- src/server/responses/core.ts

printf '%s\n' '--- exact integration-symbol references in tests ---'
rg -n 'shouldRetryCodexPoolAccountQuota|retryCodexPoolOnAlternateAccount' tests/ || true

printf '%s\n' '--- quota test ---'
nl -ba tests/codex-quota-rejection.test.ts | sed -n '1,220p'

printf '%s\n' '--- core.ts relevant definitions and call sites ---'
rg -n -C 20 'shouldRetryCodexPoolAccountQuota|retryCodexPoolOnAlternateAccount|classifyCodexPreStreamRejection' src/server/responses/core.ts

Repository: lidge-jun/opencodex

Length of output: 8082


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- repository state ---'
git status --short
git log -1 --oneline --stat

printf '%s\n' '--- quota test ---'
sed -n '1,220p' tests/codex-quota-rejection.test.ts

printf '%s\n' '--- core retry implementation ---'
sed -n '230,390p' src/server/responses/core.ts
sed -n '1600,1685p' src/server/responses/core.ts

printf '%s\n' '--- tests that exercise response handling or pool retries indirectly ---'
rg -n -C 5 'handleResponses|handleResponse|poolRetryOutcome|alternate.account|alternateAccount|Codex.*pool|pool.*Codex|retry.*account|account.*retry' tests/ -g '*.test.ts' | head -n 500

printf '%s\n' '--- commit changes for the relevant files ---'
git diff HEAD^ HEAD --stat -- src/server/responses/core.ts tests/codex-quota-rejection.test.ts
git diff HEAD^ HEAD -- src/server/responses/core.ts tests/codex-quota-rejection.test.ts | sed -n '1,260p'

Repository: lidge-jun/opencodex

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- commits that introduced the core retry symbols ---'
git log --oneline -8 -- src/server/responses/core.ts
git log --oneline -8 -S'shouldRetryCodexPoolAccountQuota' -- src/server/responses/core.ts

printf '%s\n' '--- candidate integration tests ---'
python3 - <<'PY'
from pathlib import Path

for path in sorted(Path("tests").glob("*.test.ts")):
    text = path.read_text()
    if "handleResponses" in text and (
        "codexAccounts" in text
        or "authMode: \"forward\"" in text
        or "chatgpt.com/backend-api/codex" in text
    ):
        print(path)
PY

printf '%s\n' '--- focused Codex response tests ---'
rg -l 'handleResponses|chatgpt\.com/backend-api/codex|authMode: "forward"|codexAccounts' tests/ -g '*.test.ts' \
  | sort \
  | head -n 80

printf '%s\n' '--- relevant test descriptions and setup ---'
rg -n -C 3 'test\(|describe\(|handleResponses|chatgpt\.com/backend-api/codex|authMode: "forward"|codexAccounts' \
  tests/server*.test.ts tests/*response*.test.ts tests/*codex*.test.ts 2>/dev/null \
  | head -n 800

Repository: lidge-jun/opencodex

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- core retry commit ---'
git show --stat --oneline 0b10fe19
git show --format=fuller --no-ext-diff 0b10fe19 -- src/server/responses/core.ts tests/ | sed -n '1,420p'

printf '%s\n' '--- quota-related response tests ---'
rg -n -C 8 'usage_limit_exceeded|insufficient_quota|Retry-After|retry-after|status: 429|status: 402|status: 400|handleResponses|globalThis.fetch' \
  tests/chat-completions-endpoint.test.ts \
  tests/effort-policy.test.ts \
  tests/responses-compaction-routing.test.ts \
  tests/server-combo-failover-e2e.test.ts \
  tests/subagent-fallback-handle-responses.test.ts \
  tests/v2-agent-message-failfast.test.ts \
  2>/dev/null || true

printf '%s\n' '--- all test references to the quota semantics ---'
rg -n -C 4 'usage_limit_exceeded|insufficient_quota|reset-eligible|quota rejection|quota.*retry|retry.*quota|alternate.*account' tests/ -g '*.test.ts' || true

printf '%s\n' '--- exact line numbers in core.ts ---'
awk 'NR >= 245 && NR <= 260 || NR >= 1620 && NR <= 1665 { printf "%d:%s\n", NR, $0 }' src/server/responses/core.ts

Repository: lidge-jun/opencodex

Length of output: 50376


Add handleResponses integration coverage for Codex pool retries.

tests/codex-quota-rejection.test.ts:12-13 only exercises classifyCodexPreStreamRejection. Add focused tests for src/server/responses/core.ts:250-255 and :1625-1639 that verify a reset-eligible 429 or 402 retries on an alternate account, while a non-retryable status preserves the original status and body.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/codex-quota-rejection.test.ts` around lines 12 - 13, Extend the test
suite in “Codex pre-stream quota rejection classification” with handleResponses
integration coverage for Codex pool retries. Exercise the reset-eligible 429 and
402 paths in handleResponses, asserting each retries on an alternate account,
and add a non-retryable-status case asserting the original status and response
body are preserved.

Source: Path instructions

@luvs01
luvs01 force-pushed the feat/657-reset-eligible-exhaustion branch from 08949c0 to aef44bd Compare August 2, 2026 03:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants