Handle Blockscout PRO API credit-exhaustion as HTTP 402#399
Conversation
…core Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 16 minutes and 56 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the 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. WalkthroughAdds CreditsExhaustedError and maps Blockscout PRO HTTP 402 to it in the shared request core (no retry), returns HTTP 402 from the REST handler, updates docs and versions, and adds tests covering GET/POST/metadata and tool-level graceful degradation. ChangesHTTP 402 Credit Exhaustion Error Handling
Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Revert the three __init__.py files added in Phase 3 and rename tests/api/test_helpers.py -> tests/api/test_api_helpers.py to disambiguate it from tests/tools/transaction/test_helpers.py under pytest's prepend import mode. This keeps the test tree flat and consistent (no partial-package asymmetry) instead of relying on a subset of __init__.py files. Update the AGENTS.md tree accordingly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
SPEC.md (1)
605-616: 💤 Low valueDocumentation captures the essential credit-exhaustion semantics.
The new section correctly documents the 402 mapping, no-retry behavior, plan-agnostic design, and surface-mode differences. All claims are verified by the implementation (context snippets confirm the shared-core mapping, REST handler 402 response, and composite-tool downgrade).
The composite-tool paragraph (lines 615-616) is somewhat detailed but provides important context for users debugging partial-success scenarios. Based on learnings, SPEC.md prefers concise high-level descriptions over detailed technical explanations.
Optional: Condense the composite-tool paragraph
If you prefer tighter prose, the composite-tool behavior could be condensed:
- Note on composite tools: several tools issue multiple PRO API requests concurrently via `asyncio.gather(..., return_exceptions=True)`, with exactly one hard-fail *primary* request whose exception is re-raised and surfaces directly, plus one or more optional *side* requests whose isolated failures are each downgraded to a null field plus an explanatory note. This applies to `get_address_info` (primary: address-info; side: metadata and first-transaction), `get_block_info` with `include_transactions=True` (primary: block details; side: the transactions list), and `get_transaction_info` (primary: the transaction; side: account-abstraction user operations). Because the side requests share the same `_make_blockscout_http_request` core, a `402` raised by a side request *alone* is mapped to `CreditsExhaustedError` and then absorbed into a note (partial data is still returned) rather than surfaced as a hard error. Under genuine credit exhaustion, however, every request — including the primary one — is rejected, so the `CreditsExhaustedError` still reaches the client through the primary request's re-raise. + Note on composite tools: tools that issue concurrent requests (e.g., `get_address_info`) map a `402` from optional side requests into a note with partial data returned. Under genuine credit exhaustion, the primary request also fails with `CreditsExhaustedError`, so the error still surfaces.This preserves the key insight (side-request downgrade vs primary hard-fail) without enumerating every affected tool.
🤖 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 `@SPEC.md` around lines 605 - 616, The composite-tool paragraph is too detailed; trim it to a concise high-level statement that side requests are downgraded to notes while the primary request hard-fails on CreditsExhaustedError. Update the paragraph referencing the shared core _make_blockscout_http_request and the CreditsExhaustedError mapping, and keep the examples minimal by mentioning the affected helpers (get_address_info, get_block_info, get_transaction_info) only as a short illustrative list rather than enumerating primary vs side per tool.
🤖 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 `@SPEC.md`:
- Around line 605-616: The composite-tool paragraph is too detailed; trim it to
a concise high-level statement that side requests are downgraded to notes while
the primary request hard-fails on CreditsExhaustedError. Update the paragraph
referencing the shared core _make_blockscout_http_request and the
CreditsExhaustedError mapping, and keep the examples minimal by mentioning the
affected helpers (get_address_info, get_block_info, get_transaction_info) only
as a short illustrative list rather than enumerating primary vs side per tool.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: b7357dc2-0bf9-4d75-b451-af9b2b850d15
📒 Files selected for processing (17)
AGENTS.mdAPI.mdSPEC.mdblockscout_mcp_server/__init__.pyblockscout_mcp_server/api/helpers.pyblockscout_mcp_server/tools/common.pypyproject.tomlserver.jsontests/api/test_api_helpers.pytests/api/test_routes.pytests/tools/address/test_get_address_info.pytests/tools/address/test_get_address_info_metadata.pytests/tools/block/test_get_block_info.pytests/tools/test_common_blockscout_request.pytests/tools/test_common_metadata.pytests/tools/test_common_post_request.pytests/tools/transaction/test_get_transaction_info.py
…tion Replace four verbose paragraphs (~350 words) with two concise sentences covering the shared-core mapping, no-retry behaviour, client surfaces, and composite-tool primary-vs-side semantics (~60 words). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@SPEC.md`:
- Line 607: The spec text at `_make_blockscout_http_request` is inconsistent: it
currently says “any 402 response” maps to `CreditsExhaustedError` but earlier
(line 232) limits this to responses with body `{"error":"Out of credits"}`;
update the `_make_blockscout_http_request` section to match the earlier contract
by specifying that only HTTP 402 responses whose body/error field equals `"Out
of credits"` (or an equivalent structured indicator) are mapped to
`CreditsExhaustedError` (and thereby propagate without retries to REST and
native MCP clients), rather than mapping every 402.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
Line 607 said "any 402 response" which contradicts the contract at line 232
(only HTTP 402 with body {"error":"Out of credits"} maps to
CreditsExhaustedError). Align the description to match the authoritative
contract.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
What & why
Closes #393.
The Blockscout PRO API meters access by credits and returns HTTP 402 with body
{"error": "Out of credits"}when the daily allowance for the server's API key is exhausted. Previously this was treated like a generic transient upstream failure, so it could be retried and surfaced as an opaque error. This change makes credit exhaustion a distinct, clearly-labeled signal that propagates immediately (no retries) and is surfaced to REST clients as402 Payment Required.Changes
75d3d5e): Added a dedicatedCreditsExhaustedError(a directExceptionsubclass, not aValueError) alongsideResponseTooLargeError. The shared_make_blockscout_http_requestcore now maps any402toCreditsExhaustedErrorat the start of itshttpx.HTTPStatusErrorhandler — before enrichment and outside the retry set — so it applies uniformly to all PRO API helpers (make_blockscout_request,make_blockscout_post_request,make_metadata_request) and never triggers a retry.Raises:docstrings updated on the three public helpers.4460cf4): Thehandle_rest_errorsdecorator mapsCreditsExhaustedErrortoJSONResponse({"error": ...}, status_code=402), mirroring the existingResponseTooLargeError → 413shape and ordered before the generic catch-all.9d84fe9): SPEC.md (§7 enrichment sentence, §8 new "Credit Exhaustion" subsection, error-semantics revision), API.md (new402 Payment Requirederror category), AGENTS.md (tests/api/tree). Also added three empty__init__.pyfiles (tests/api/,tests/tools/,tests/tools/transaction/) to resolve a test module-name collision between the newtests/api/test_helpers.pyand the pre-existingtests/tools/transaction/test_helpers.py.33881cf):0.16.0.dev14→0.16.0.dev15inpyproject.toml,blockscout_mcp_server/__init__.py, andserver.json.Testing
638 passed(new core-helper tests assertCreditsExhaustedError, exactly-once invocation, and no retry; REST helper + route tests assert the402mapping and handler ordering).85 passed, 0 failed, 0 skipped, 0 timed out.ruff check .andruff format --check .: clean.Reviewer notes
402path itself is not integration-testable — credit exhaustion cannot be forced on the shared deployment key — so it is covered by unit tests only. The integration run guards against regressions in the shared_make_blockscout_http_requestcore that this change touches.402from this gate, which is acceptable (see SPEC.md §8).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests
Chores