Skip to content

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

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

feat(codex): classify reset-eligible quota rejection#866
luvs01 wants to merge 3 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: 64999d5f-33dc-47f0-9a30-7b8351433ec6

📥 Commits

Reviewing files that changed from the base of the PR and between cfe9bd0 and fe44ece.

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

📝 Walkthrough

Walkthrough

Codex pre-stream responses now receive strict structured rejection classification. The classifier fails closed for invalid, unavailable, oversized, consumed, or cancelled bodies. The existing 402/429 pool-account retry predicate is now exported. Tests cover 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, transient server, rate-limit, billing/quota, and other responses. Only exact exhaustion codes on HTTP 429 or 402 receive reset-credit eligibility.
Retry export and validation
src/server/responses/core.ts, tests/codex-quota-rejection.test.ts
Exports the existing 402/429 retry predicate. Tests cover exact schemas, ambiguous payloads, malformed and cancelled bodies, status handling, and response preservation.

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

Suggested reviewers: ingwannu, lidge-jun, wibias

Sequence Diagram(s)

sequenceDiagram
  participant CodexResponse
  participant QuotaClassifier
  participant RejectionResult
  CodexResponse->>QuotaClassifier: status and response body
  QuotaClassifier->>RejectionResult: classified rejection and eligibility
Loading
🚥 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 thread src/server/responses/core.ts Outdated
Comment thread tests/codex-quota-rejection.test.ts
@luvs01
luvs01 force-pushed the feat/657-reset-eligible-exhaustion branch 2 times, most recently from aef44bd to cfe9bd0 Compare August 2, 2026 09:50
@luvs01

luvs01 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

All previously requested changes have been applied to the current head (fe44ece), all checks are green, and no review threads remain. When convenient, could you please re-review this revision? Thank you.

@Wibias

Wibias commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

[shipping-github] Verdict: changes-requested

PR: #866 - feat(codex): classify reset-eligible quota rejection
Head: fe44ece on dev (mergeable: MERGEABLE, mergeStateStatus: CLEAN)

Semantic propagation

  • Concepts audited: reset-eligible exhaustion signal (usage_limit_exceeded / insufficient_quota), pre-stream rejection classification, Multi-account failover issue: Session freezes on depleted primary account despite active secondary Pro account #584 pool alternate-account retry predicate, fail-closed bounded body reading
  • Authoritative sources: issue #657 (allowlist requirement), src/codex/quota-rejection.ts (RESET_ELIGIBLE_CODES at line 24), existing error classification src/lib/errors.ts (intentionally broader: text-based), src/server/responses/core.ts (shouldRetryCodexPoolAccountQuota line 249, retry at 1628)
  • Producers and consumers checked: classifyCodexPreStreamRejection has no production call site on this head (tests only); the Multi-account failover issue: Session freezes on depleted primary account despite active secondary Pro account #584 retry stays status-only (402/429) - behavior identical to base, only the export changed; readBoundedResponseBody is pre-existing with its own test file
  • Public/derived representations checked: none new (no API, no serialization, no logging of bodies); codexQuotaOutcomeMeta/header-based quota recording unchanged
  • Material variant partitions checked: code-only, type-only, code+type equal, nested error, root-level, non-string values, unknown values, message-text-only, Retry-After 429, 401/403/5xx/other statuses, malformed/aborted bodies, ambiguous/conflicting schemas
  • Positive and negative assertions checked: positive 6, near-miss 4, ambiguous 4, disagreement 4, message-only, malformed, aborted, status separations, sync retry predicate (4 rows)
  • Unmapped surfaces: none
  • Unproven equivalence assumptions: none
  • Representation mismatches: PR description bullet "connect the bounded classifier to the existing Multi-account failover issue: Session freezes on depleted primary account despite active secondary Pro account #584 retry" does not match the head - fe44ece deliberately un-wired the classifier (retry is status-only again); the classifier is standalone groundwork for Add opt-in rejection-only quota recovery with automatic reset-credit redemption #657
  • Variant coverage gaps: classifier-level negatives for oversized / truncated / consumed bodies exist only indirectly (helper-level in bounded-body.test.ts); the PR claims these fail closed
  • Axis verdict: pass with the description mismatch carried into Spec/Standards

Linked: refs #657 (no closing issues)

Usefulness

Useful and correctly scoped groundwork: this is the maintainer-requested first slice of #657 (semantic classification only, no credit spending, no waiting, no policy), and the classifier is deliberately strict - exact structured codes on 429/402 only, fail-closed on ambiguity, malformed, oversized, consumed, or cancelled bodies. The earlier maintainer findings (case/whitespace normalization, root/nested ambiguity, missing negatives) are all addressed on this head.

Bugs / correctness

  • Method: bug-review.md - Bugbot: n/a (Codex host) - complementary lenses: done (silent_failures, resource_leaks, edge_cases, error propagation, cancellation)
  • Findings: none confirmed. resetEligibleCodeFromResponse clones the response and bails on displaySafe=false/truncated/empty; JSON.parse failures and clone/read throws fail closed; original response stays intact for passthrough (verified: retryCodexPoolOnAlternateAccount cancels the body only when an alternate account exists)
  • Fixed this session: none (review-only run)

Security

  • Scope reviewed: business logic, authz, secrets/config, error mapping (scope: full)
  • Findings: none confirmed. No admission/authz change; bounded 64 KiB / 5 s body reads on a clone; no body text, account identifier, or credential logged; bun run privacy:scan green
  • Fixed this session: none

Spec / standards

Reviews

  • Owners/maintainers: @lidge-jun requested changes on the earlier head (issue comment 5154104384); every requested item (exact matching, ambiguity fail-closed, negative tests) is implemented on fe44ece, and @luvs01 pinged for re-review, but the maintainer has not re-reviewed yet - the authoritative gate still blocks on this
  • Bots (CodeRabbit/Codex): 2/2 threads resolved; latest CodeRabbit review clean; the earlier ask for handleResponses integration coverage is satisfied by existing server-auth.test.ts cases (#584: pre-stream 429 retries once on another eligible pool account and the no-alternate preservation case)

Base / CI

  • Behind/conflicts: head is 294 commits behind current dev tip (56b418187) - no conflicts, but an update from base is needed before merge
  • Required checks: green on head fe44ece - ubuntu, windows, macos, npm-global x3, react-doctor, enforce-target, label, CodeRabbit
  • Local tip compile/tests: bun x tsc --noEmit pass; 105 focused tests pass (quota-rejection, server-auth, bounded-body, error-fidelity); bun run privacy:scan pass

Gate

Ship gate: blocked - wake:trusted_human_feedback_needs_code (maintainer issue comment 5154104384, addressed in code but not yet cleared by a re-review). Not draft, no WIP/do-not-merge.

Bottom line

The code is sound, well-tested, and security-clean: the classifier is fail-closed, #584 behavior is provably unchanged, and CI is green on the head. Three items stand between this PR and merge-ready: (1) @lidge-jun re-review to clear the gate (the author already asked), (2) a one-bullet PR-description correction so the summary matches the unwired classifier on the head, (3) optional but cheap - classifier-level negatives for oversized/truncated/consumed bodies, plus an update from dev before merge.

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