Skip to content

fix(usage): treat 402 from the usage-report endpoint as non-retryable#175

Open
khaledosman wants to merge 2 commits into
mainfrom
fix/usage-report-402-non-retryable
Open

fix(usage): treat 402 from the usage-report endpoint as non-retryable#175
khaledosman wants to merge 2 commits into
mainfrom
fix/usage-report-402-non-retryable

Conversation

@khaledosman

@khaledosman khaledosman commented Jun 16, 2026

Copy link
Copy Markdown
Member

Description

Add 402 to _USAGE_NON_RETRYABLE_STATUS_CODES in _platform.py (now {401, 402, 404, 409, 422}), plus a unit test asserting the usage reporter POSTs once and gives up on a 402.

A 402 (payment required / insufficient balance) returned by the platform's /gateway/usage endpoint is a permanent rejection: the org wallet is overdrawn or missing and won't recover within the retry window, so retrying only hammers the platform. It belongs alongside the other permanent-rejection codes (401/404/409/422) the reporter already short-circuits on.

This is behavior-preserving today402 is already excluded from retry by the should_retry = response.status_code >= 500 predicate in _report_platform_usage, so it isn't retried even now. The change makes the intent explicit in the documented set and keeps the usage reporter robust if that >= 500 predicate is ever changed to retry 4xx.

Companion to a platform-side fix where an over-budget managed completion now settles the already-incurred charge (driving the org wallet to zero/negative) instead of dropping it, so /gateway/usage returns 204 for that request rather than 402. This PR hardens handling of the remaining 402 cases (e.g. a missing wallet) on the usage-report path. Distinct from #173, which makes 404/405/409/410 retryable on the provider-fallback path (_FALLBACK_RETRYABLE_STATUS_CODES) — different set, non-overlapping hunks.

PR Type

  • New Feature
  • Bug Fix
  • Refactor
  • Documentation
  • Infrastructure / CI

Relevant issues

No otari-side issue. Companion to platform-side fix mozilla-ai/any-llm-platform#1105.

Checklist

  • I understand the code I am submitting.
  • I have added or updated tests that cover my change (tests/unit, tests/integration).
  • I ran lint, typecheck, and the relevant unit tests locally (ruff check, mypy on the changed files, and pytest tests/unit/test_run_platform_attempts.py). I did not run the full integration suite.
  • Documentation was updated where necessary. (No docs change needed.)
  • If the API contract changed, I regenerated the OpenAPI spec. (No API contract change.)

AI Usage

  • No AI was used.
  • AI was used for drafting/refactoring.
  • This is fully AI-generated.

AI Model/Tool used: Claude Code (Claude Opus 4.8)

Any additional AI details you'd like to share: Authored as a companion to the platform-side billing fix referenced above.

  • I am an AI Agent filling out this form (check box if true)

A 402 (payment required / insufficient balance) returned by the platform's
/gateway/usage endpoint is a permanent rejection: an overdrawn or missing org
wallet won't recover within the retry window, so retrying only hammers the
platform. Add 402 to _USAGE_NON_RETRYABLE_STATUS_CODES alongside 401/404/409/422.

This is behavior-preserving today (402 is already excluded by the >= 500 retry
predicate), but makes the intent explicit and keeps the usage reporter robust to
future changes in that predicate. Add a unit test asserting a single POST with
no retry on 402.

Created with Claude Code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@khaledosman khaledosman temporarily deployed to integration-tests June 16, 2026 11:05 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@khaledosman, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 46 minutes and 56 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a0c2c851-e216-41d7-bb06-15932a1c8912

📥 Commits

Reviewing files that changed from the base of the PR and between b9b2c6b and 52b9d7b.

📒 Files selected for processing (2)
  • src/gateway/api/routes/_platform.py
  • tests/unit/test_run_platform_attempts.py

Walkthrough

HTTP 402 is added to _USAGE_NON_RETRYABLE_STATUS_CODES in the platform usage-reporting route, with the comment updated to explain the explicit inclusion. A new async unit test verifies that _report_platform_usage issues exactly one POST and never awaits a retry sleep when the endpoint returns 402.

Changes

402 Non-Retry Policy and Test Coverage

Layer / File(s) Summary
Expand non-retryable status codes to include 402
src/gateway/api/routes/_platform.py
Adds HTTP 402 to _USAGE_NON_RETRYABLE_STATUS_CODES and revises the comment to note that 402 is already outside the >=500 retry range but is listed explicitly to preserve intended semantics.
Unit test: no retry on 402 response
tests/unit/test_run_platform_attempts.py
Adds asyncio, SimpleNamespace, and AsyncMock imports, then adds test_report_platform_usage_does_not_retry_on_402, which stubs _post_platform to return 402, patches asyncio.sleep, and asserts a single POST with no sleep calls.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title follows the Conventional Commit prefix format (fix:), uses imperative mood, and is 69 characters—within the ~70 char guideline. It clearly describes the main change.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Description check ✅ Passed The PR description is comprehensive, well-structured, and follows all required template sections with detailed context about the change and its rationale.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/usage-report-402-non-retryable
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/usage-report-402-non-retryable

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions github-actions Bot added missing-template PR is missing required template sections and removed missing-template PR is missing required template sections labels Jun 16, 2026
@khaledosman khaledosman requested a review from Copilot June 16, 2026 11:07

@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.

🧹 Nitpick comments (1)
src/gateway/api/routes/_platform.py (1)

586-591: ⚡ Quick win

Docstring should explicitly include payment-required as non-retryable.

_USAGE_NON_RETRYABLE_STATUS_CODES now includes 402, but the _report_platform_usage docstring still documents only auth/not-found/conflict/unprocessable. Aligning this text avoids future drift and makes intent clearer for maintainers.

Suggested docstring tweak
-    impact the user's response path. Non-retryable status codes (auth /
-    not-found / conflict / unprocessable) short-circuit the retry loop.
+    impact the user's response path. Non-retryable status codes (auth /
+    payment-required / not-found / conflict / unprocessable) short-circuit
+    the retry loop.
🤖 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/gateway/api/routes/_platform.py` around lines 586 - 591, Update the
docstring for the `_report_platform_usage` function to explicitly include
"payment-required" (402 status code) in the list of non-retryable status codes.
The docstring currently lists only auth/not-found/conflict/unprocessable as
non-retryable, but the `_USAGE_NON_RETRYABLE_STATUS_CODES` constant now includes
402. Modify the docstring text to add payment-required to this list so that the
documentation accurately reflects the actual implementation.
🤖 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.

Nitpick comments:
In `@src/gateway/api/routes/_platform.py`:
- Around line 586-591: Update the docstring for the `_report_platform_usage`
function to explicitly include "payment-required" (402 status code) in the list
of non-retryable status codes. The docstring currently lists only
auth/not-found/conflict/unprocessable as non-retryable, but the
`_USAGE_NON_RETRYABLE_STATUS_CODES` constant now includes 402. Modify the
docstring text to add payment-required to this list so that the documentation
accurately reflects the actual implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bd438e07-05ea-4ef7-809b-720abc6e678f

📥 Commits

Reviewing files that changed from the base of the PR and between c2d3eb2 and b9b2c6b.

📒 Files selected for processing (2)
  • src/gateway/api/routes/_platform.py
  • tests/unit/test_run_platform_attempts.py

Copilot AI 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.

Pull request overview

This PR makes the platform usage reporter explicitly treat HTTP 402 Payment Required responses from the platform /gateway/usage endpoint as non-retryable, and adds a unit test to pin the intended behavior.

Changes:

  • Add 402 to _USAGE_NON_RETRYABLE_STATUS_CODES in src/gateway/api/routes/_platform.py.
  • Add a unit test asserting that a 402 response results in a single POST attempt (no retries / no backoff sleep).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/gateway/api/routes/_platform.py Expands the documented non-retryable status-code set for usage reporting to include 402.
tests/unit/test_run_platform_attempts.py Adds a unit test around _report_platform_usage behavior for 402 responses.

Comment thread tests/unit/test_run_platform_attempts.py
Comment thread src/gateway/api/routes/_platform.py
…2 in test

Address review feedback on the usage-report 402 change:
- Update the _report_platform_usage docstring to list payment-required among the
  non-retryable status codes, matching _USAGE_NON_RETRYABLE_STATUS_CODES.
- Assert 402 is in the non-retryable set so the unit test guards the
  classification itself, not just the (currently equivalent) >= 500 retry
  behaviour.

Created with Claude Code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@khaledosman khaledosman temporarily deployed to integration-tests June 16, 2026 11:18 — with GitHub Actions Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants