Skip to content

Surface low PRO API credit balance via advisory note#402

Open
akolotov wants to merge 9 commits into
mainfrom
claude/gifted-heisenberg-21b7f7
Open

Surface low PRO API credit balance via advisory note#402
akolotov wants to merge 9 commits into
mainfrom
claude/gifted-heisenberg-21b7f7

Conversation

@akolotov
Copy link
Copy Markdown
Collaborator

@akolotov akolotov commented Jun 6, 2026

Summary

Closes #394.

Surfaces a low PRO API credit balance to MCP clients as an advisory note on tool responses, so users learn they are running low on credits before requests start failing with HTTP 402.

When a Blockscout PRO API response carries an x-credits-remaining header and the captured balance falls below the configured threshold (default 5000, 0 disables), every affected tool response gains a note pointing at https://dev.blockscout.com. The mechanism is transport-agnostic and works identically in MCP (stdio) and REST (HTTP) modes.

How it works

  • Config — new pro_api_low_credits_threshold field (BLOCKSCOUT_PRO_API_LOW_CREDITS_THRESHOLD, default 5000, ge=0).
  • Capture — a CreditSink (mutable box in a ContextVar) records the minimum x-credits-remaining seen on the success path of the shared HTTP core. Capture is defensive: missing/unparseable/non-string headers are silently ignored; negative values are kept.
  • Per-invocation scope — a new @pro_api_credit_scope decorator allocates a fresh sink per tool call and resets it in finally, applied innermost on all 16 tools.
  • Emissionbuild_tool_response reads the sink and, when below a positive threshold, appends the advisory note (integer display for whole numbers) without mutating the caller's notes list.

Tests

  • Unittests/tools/test_credit_tracking.py covers the sink, capture defensiveness, the decorator (lifecycle, isolation, transport-agnosticism, functools.wraps), and note emission across thresholds/zero/negative. Cross-mode end-to-end coverage in test_credit_tracking.py (MCP) and tests/api/test_routes.py (REST) exercises the real decorator → capture → build_tool_response chain (only the HTTP transport is mocked). 751 passed.
  • Integrationtests/integration/test_common_helpers.py::test_credit_capture_on_real_pro_api_response makes a real PRO API GET and asserts a numeric balance is captured; skip-gated when no PRO key is configured. Ran live via the timeout runner: 9 passed, 0 skipped.
  • ruff check . and ruff format --check . both clean.

Notes for reviewers

  • Version bumped to 0.16.0.dev17 in pyproject.toml, blockscout_mcp_server/__init__.py, and server.json; mcpb/manifest.json intentionally unchanged.
  • Docs updated: SPEC.md, AGENTS.md, API.md, README.md, .env.example.
  • Implemented phase-by-phase; one commit per phase.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added low-credit advisory warnings to tool responses when Blockscout PRO API credit balance falls below a configured threshold (default: 5,000 credits)
  • Configuration

    • New BLOCKSCOUT_PRO_API_LOW_CREDITS_THRESHOLD environment variable (default: 5000; set to 0 to disable warnings)
  • Documentation

    • Updated docs to describe credit usage visibility and low-credit warning behavior

akolotov and others added 8 commits June 5, 2026 19:46
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>
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>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 6, 2026

Wondering what really moved? Review this PR in Change Stack to inspect semantic changes, definitions, and references.

Review Change Stack

Walkthrough

This PR implements PRO API credit-balance tracking and advisory notes. It captures the x-credits-remaining response header from Blockscout PRO API calls, tracks the minimum value observed within each tool invocation, and appends an advisory note to tool responses when the balance falls below a configurable threshold (default 5000 credits, disableable via 0).

Changes

PRO API Credit Tracking

Layer / File(s) Summary
Configuration & Core Credit-Tracking Infrastructure
.env.example, blockscout_mcp_server/config.py, blockscout_mcp_server/pro_api_key_context.py, blockscout_mcp_server/__init__.py, pyproject.toml, server.json
ServerConfig adds pro_api_low_credits_threshold field (default 5000, env BLOCKSCOUT_PRO_API_LOW_CREDITS_THRESHOLD, ge=0). New CreditSink class and module-level _credit_sink: ContextVar[CreditSink | None] enable per-invocation, cross-task credit tracking. @pro_api_credit_scope decorator establishes fresh sink on entry and resets on exit. Package versions incremented to 0.16.0.dev17.
Request & Response Credit Handling
blockscout_mcp_server/tools/common.py
_capture_credits_remaining(response) defensively parses x-credits-remaining header (case-insensitive, non-string/non-numeric safe) and records into active sink, capturing minimum value. On successful PRO API responses, this helper is invoked after status validation. build_tool_response checks if threshold > 0 and remaining < threshold, then appends formatted advisory note to response notes without mutating caller's list.
Tool Decorator Wiring
blockscout_mcp_server/tools/address/get_address_info.py, blockscout_mcp_server/tools/address/get_tokens_by_address.py, blockscout_mcp_server/tools/address/nft_tokens_by_address.py, blockscout_mcp_server/tools/block/get_block_info.py, blockscout_mcp_server/tools/block/get_block_number.py, blockscout_mcp_server/tools/chains/get_chains_list.py, blockscout_mcp_server/tools/contract/get_contract_abi.py, blockscout_mcp_server/tools/contract/inspect_contract_code.py, blockscout_mcp_server/tools/contract/read_contract.py, blockscout_mcp_server/tools/direct_api/direct_api_call.py, blockscout_mcp_server/tools/ens/get_address_by_ens_name.py, blockscout_mcp_server/tools/initialization/unlock_blockchain_analysis.py, blockscout_mcp_server/tools/search/lookup_token_by_symbol.py, blockscout_mcp_server/tools/transaction/get_token_transfers_by_address.py, blockscout_mcp_server/tools/transaction/get_transaction_info.py, blockscout_mcp_server/tools/transaction/get_transactions_by_address.py
All PRO API tools now import and apply @pro_api_credit_scope: four tools (get_address_info, get_tokens_by_address, get_block_info, get_address_by_ens_name) replace @pro_api_key_scope with @pro_api_credit_scope; thirteen tools stack @pro_api_credit_scope alongside the existing @pro_api_key_scope.
Tests & Documentation
AGENTS.md, API.md, README.md, SPEC.md, .env.example, tests/test_config.py, tests/integration/test_common_helpers.py, tests/api/test_routes.py, tests/tools/test_credit_tracking.py
Documentation updated to describe credit-capture mechanism, low-credits advisory behavior, and configurable threshold. tests/test_config.py validates default/env override/rejection of negative thresholds. tests/integration/test_common_helpers.py adds real-API capture test. tests/api/test_routes.py adds REST-mode end-to-end low-credits scenario. tests/tools/test_credit_tracking.py (981 lines) comprehensively tests CreditSink semantics, cross-task visibility in asyncio/anyio contexts, header capture robustness, decorator isolation and metadata preservation, low-credits note presence/absence conditions, and MCP/REST flows.

Possibly related issues

  • blockscout/mcp-server#382: Main umbrella issue for PRO API credit handling; this PR fully implements part 2 (low-credits advisory).

Possibly related PRs

  • blockscout/mcp-server#384: Both PRs modify blockscout_mcp_server/tools/common.py's PRO request/response pipeline; this PR layers low-credits advisory on top of earlier refactoring work.
  • blockscout/mcp-server#400: Both PRs extend the same PRO request-scoping infrastructure (pro_api_key_context.py, tool decorators, tools/common.py), though with different scope concerns.
  • blockscout/mcp-server#364: Both PRs touch core tool-execution surfaces including blockscout_mcp_server/tools/common.py and direct API call wiring.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.33% 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 accurately and concisely describes the main change: surfacing low PRO API credit balance via an advisory note in tool responses.
Linked Issues check ✅ Passed The PR implements all core objectives from issue #394: config field with threshold, credit capture via ContextVar-stored mutable sink, per-invocation isolation via decorator, advisory note emission in responses, and comprehensive tests across MCP/REST modes.
Out of Scope Changes check ✅ Passed All changes are in scope for issue #394: credit-tracking infrastructure, HTTP-transport tools decorator updates, documentation updates, configuration, and test coverage. No unrelated changes detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/gifted-heisenberg-21b7f7

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.

A non-finite `x-credits-remaining` header value (e.g. `-Infinity`) parsed by
`_capture_credits_remaining` reached `CreditSink` and caused two defects:

- `-inf` later hit `int(remaining)` in the low-credits display branch of
  `build_tool_response`, raising `OverflowError` and turning a successful tool
  call into a hard error.
- A `nan`/`-inf` recorded first poisoned the running minimum: `value <
  self.remaining` is `False` for every later observation, so a genuine low
  balance was dropped and the advisory note silently suppressed for the whole
  invocation.

Guard at the single chokepoint `CreditSink.record`: non-finite values are now
ignored, enforcing the invariant "remaining is None or a finite float". This
fixes both the crash and the suppression without touching `build_tool_response`.

Add unit tests (non-finite ignored, minimum not poisoned) and an end-to-end
regression test that a non-finite captured header neither crashes nor emits a
note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@akolotov akolotov force-pushed the claude/gifted-heisenberg-21b7f7 branch from 396363b to a3bd14c Compare June 6, 2026 04:28
@akolotov akolotov self-assigned this Jun 6, 2026
@akolotov akolotov marked this pull request as ready for review June 6, 2026 04:40
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/tools/test_credit_tracking.py (1)

1-982: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Split this test module; it exceeds the project’s unit-test size limit.

This file is ~982 LOC, which is well past the 500 LOC cap for unit test modules. Please split it into focused modules (e.g., test_credit_sink.py, test_credit_capture_helpers.py, test_credit_scope_decorator.py, test_build_tool_response_credit_note.py, test_credit_note_e2e_mcp.py) to keep maintenance and failure triage manageable.

As per coding guidelines, tests/**/*.py: “Unit test files must not exceed 500 LOC; split into multiple focused modules when approaching the limit.”

🤖 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/tools/test_credit_tracking.py` around lines 1 - 982, Large test module
exceeds 500 LOC limit; split into several focused test modules grouped by
concern (e.g., CreditSink behavior, HTTP capture helpers, helper-surface
routing, pro_api_credit_scope decorator behavior, build_tool_response note
logic, and end-to-end MCP flow). For each new module, move the relevant tests
and any small shared fixtures/helpers they need (e.g., _set_sink, _MockResponse,
_SimpleClient/_PostClient/_GetClient/_AlternatingClient, HELPER_IDS) or extract
truly shared pieces into a new tests/helpers module and import them; ensure
imports reference the same symbols used in the tests (CreditSink, _credit_sink,
make_blockscout_request, make_blockscout_post_request, make_metadata_request,
_capture_credits_remaining, build_tool_response, pro_api_credit_scope,
get_block_number) so tests keep their assertions intact; run/adjust patches and
pytest markers after splitting to preserve test isolation and names.

Source: Coding guidelines

🤖 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 `@tests/api/test_routes.py`:
- Line 873: A function-local import of "from blockscout_mcp_server.config import
config as bms_config" was added inside a test; move that import to the
module-level import block at the top of tests/api/test_routes.py (so bms_config
is imported with the other top-level imports) and remove the local import; if
you encounter circular-import issues after moving it, resolve them by using a
module-level import guard (e.g., import in TYPE_CHECKING or refactor the
dependency) so the import remains at the top.

In `@tests/test_config.py`:
- Around line 147-154: The test function
test_pro_api_low_credits_threshold_negative_rejected currently performs
function-local imports of ValidationError and pytest; hoist "from pydantic
import ValidationError" and "import pytest" to module scope (file header) and
remove the in-test imports so the test simply uses ValidationError and pytest
when calling ServerConfig in the with pytest.raises(...) block.

In `@tests/tools/test_credit_tracking.py`:
- Around line 433-460: In test_cross_task_visibility_via_periodic_progress add
assertions that mock_ctx.report_progress was called the expected number of times
to enforce progress-contract checks: after awaiting
make_request_with_periodic_progress (which uses make_blockscout_request and the
shared CreditSink), assert mock_ctx.report_progress.call_count (or specific
call_args if needed) matches the expected calls (e.g., at least one or a
specific count based on total_duration_hint); locate the test function
test_cross_task_visibility_via_periodic_progress and update its teardown
assertions to include mock_ctx.report_progress assertions so progress reporting
regressions are caught.

---

Outside diff comments:
In `@tests/tools/test_credit_tracking.py`:
- Around line 1-982: Large test module exceeds 500 LOC limit; split into several
focused test modules grouped by concern (e.g., CreditSink behavior, HTTP capture
helpers, helper-surface routing, pro_api_credit_scope decorator behavior,
build_tool_response note logic, and end-to-end MCP flow). For each new module,
move the relevant tests and any small shared fixtures/helpers they need (e.g.,
_set_sink, _MockResponse,
_SimpleClient/_PostClient/_GetClient/_AlternatingClient, HELPER_IDS) or extract
truly shared pieces into a new tests/helpers module and import them; ensure
imports reference the same symbols used in the tests (CreditSink, _credit_sink,
make_blockscout_request, make_blockscout_post_request, make_metadata_request,
_capture_credits_remaining, build_tool_response, pro_api_credit_scope,
get_block_number) so tests keep their assertions intact; run/adjust patches and
pytest markers after splitting to preserve test isolation and names.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9a124c4a-8953-474e-ba62-bb82d82b71c2

📥 Commits

Reviewing files that changed from the base of the PR and between 9e75aae and a3bd14c.

📒 Files selected for processing (31)
  • .env.example
  • AGENTS.md
  • API.md
  • README.md
  • SPEC.md
  • blockscout_mcp_server/__init__.py
  • blockscout_mcp_server/config.py
  • blockscout_mcp_server/pro_api_key_context.py
  • blockscout_mcp_server/tools/address/get_address_info.py
  • blockscout_mcp_server/tools/address/get_tokens_by_address.py
  • blockscout_mcp_server/tools/address/nft_tokens_by_address.py
  • blockscout_mcp_server/tools/block/get_block_info.py
  • blockscout_mcp_server/tools/block/get_block_number.py
  • blockscout_mcp_server/tools/chains/get_chains_list.py
  • blockscout_mcp_server/tools/common.py
  • blockscout_mcp_server/tools/contract/get_contract_abi.py
  • blockscout_mcp_server/tools/contract/inspect_contract_code.py
  • blockscout_mcp_server/tools/contract/read_contract.py
  • blockscout_mcp_server/tools/direct_api/direct_api_call.py
  • blockscout_mcp_server/tools/ens/get_address_by_ens_name.py
  • blockscout_mcp_server/tools/initialization/unlock_blockchain_analysis.py
  • blockscout_mcp_server/tools/search/lookup_token_by_symbol.py
  • blockscout_mcp_server/tools/transaction/get_token_transfers_by_address.py
  • blockscout_mcp_server/tools/transaction/get_transaction_info.py
  • blockscout_mcp_server/tools/transaction/get_transactions_by_address.py
  • pyproject.toml
  • server.json
  • tests/api/test_routes.py
  • tests/integration/test_common_helpers.py
  • tests/test_config.py
  • tests/tools/test_credit_tracking.py

Comment thread tests/api/test_routes.py
itself (with AsyncMock returning a pre-built ToolResponse) would bypass the
decorator/capture path and produce no advisory note.
"""
from blockscout_mcp_server.config import config as bms_config
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Move the function-local import to the module import block.

Line 873 introduces a local import, which breaks this repository’s import-placement rule for Python modules. Please move from blockscout_mcp_server.config import config as bms_config to the top import section.

As per coding guidelines: “ALL import statements must be placed at the top of the Python module, immediately after the module docstring (if present) and before any other code.”

🤖 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/api/test_routes.py` at line 873, A function-local import of "from
blockscout_mcp_server.config import config as bms_config" was added inside a
test; move that import to the module-level import block at the top of
tests/api/test_routes.py (so bms_config is imported with the other top-level
imports) and remove the local import; if you encounter circular-import issues
after moving it, resolve them by using a module-level import guard (e.g., import
in TYPE_CHECKING or refactor the dependency) so the import remains at the top.

Source: Coding guidelines

Comment thread tests/test_config.py
Comment on lines +147 to +154
def test_pro_api_low_credits_threshold_negative_rejected(monkeypatch):
from pydantic import ValidationError

monkeypatch.setenv("BLOCKSCOUT_PRO_API_LOW_CREDITS_THRESHOLD", "-1")
import pytest

with pytest.raises(ValidationError):
ServerConfig(_env_file=None)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import ast
from pathlib import Path

path = Path("tests/test_config.py")
code = path.read_text()
tree = ast.parse(code)

violations = []

def walk(node, in_local_scope=False):
    for child in ast.iter_child_nodes(node):
        child_local = in_local_scope or isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
        if isinstance(child, (ast.Import, ast.ImportFrom)) and child_local:
            violations.append(child.lineno)
        walk(child, child_local)

walk(tree)
print("Nested import lines:", violations)
PY

ruff check tests/test_config.py

Repository: blockscout/mcp-server

Length of output: 127


Move nested imports in tests/test_config.py to module scope (lines 148 and 151).
Function-local imports violate the repo import-placement rule; hoist ValidationError and pytest to the file header.

♻️ Proposed fix
+import pytest
+from pydantic import ValidationError
+
 from blockscout_mcp_server.config import ServerConfig
@@
 def test_pro_api_low_credits_threshold_negative_rejected(monkeypatch):
-    from pydantic import ValidationError
-
     monkeypatch.setenv("BLOCKSCOUT_PRO_API_LOW_CREDITS_THRESHOLD", "-1")
-    import pytest
-
     with pytest.raises(ValidationError):
         ServerConfig(_env_file=None)
🤖 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/test_config.py` around lines 147 - 154, The test function
test_pro_api_low_credits_threshold_negative_rejected currently performs
function-local imports of ValidationError and pytest; hoist "from pydantic
import ValidationError" and "import pytest" to module scope (file header) and
remove the in-test imports so the test simply uses ValidationError and pytest
when calling ServerConfig in the with pytest.raises(...) block.

Source: Coding guidelines

Comment on lines +433 to +460
async def test_cross_task_visibility_via_periodic_progress(mock_ctx):
"""Credit captured in a child task spawned by make_request_with_periodic_progress is visible to the parent.

make_request_with_periodic_progress uses an anyio task group internally.
The child task runs in a copied context, but shares the same CreditSink
object reference, so mutations are visible to the parent.
"""
from blockscout_mcp_server.tools.common import make_blockscout_request, make_request_with_periodic_progress

sink = CreditSink()
response = _MockResponse({"data": "ok"}, headers={"x-credits-remaining": "3333"})

with _set_sink(sink):
with (
patch("blockscout_mcp_server.tools.common._create_httpx_client", return_value=_SimpleClient(response)),
patch("blockscout_mcp_server.tools.common.ensure_chain_supported", AsyncMock()),
patch.object(config, "pro_api_key", "test_key"),
):
result = await make_request_with_periodic_progress(
ctx=mock_ctx,
request_function=make_blockscout_request,
request_args={"chain_id": "1", "api_path": "/api/v2/blocks/1"},
total_duration_hint=30.0,
)

assert result == {"data": "ok"}
# The value written by the child task must be visible on the parent's sink.
assert sink.remaining == 3333.0
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add explicit progress-report assertions in tests that use mock_ctx.

These tests validate response/note behavior but don’t assert mock_ctx.report_progress calls, so progress-contract regressions can slip through unnoticed. Add call-count assertions in these mock_ctx-based tests.

Suggested assertions
@@
 async def test_cross_task_visibility_via_periodic_progress(mock_ctx):
@@
     assert result == {"data": "ok"}
     # The value written by the child task must be visible on the parent's sink.
     assert sink.remaining == 3333.0
+    assert mock_ctx.report_progress.await_count > 0
@@
 async def test_mcp_mode_low_credits_note_in_tool_response(mock_ctx):
@@
     assert result.notes is not None
     assert any("4200" in note for note in result.notes)
     assert any("https://dev.blockscout.com" in note for note in result.notes)
+    assert mock_ctx.report_progress.await_count > 0
@@
 async def test_mcp_mode_healthy_credits_no_note_in_tool_response(mock_ctx):
@@
     # notes should be None (no advisory, no caller-supplied notes)
     assert result.notes is None
+    assert mock_ctx.report_progress.await_count > 0

As per coding guidelines, tests/**/*.py: “Assert progress tracking by verifying mock_ctx.report_progress call counts.”

Also applies to: 934-981

🤖 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/tools/test_credit_tracking.py` around lines 433 - 460, In
test_cross_task_visibility_via_periodic_progress add assertions that
mock_ctx.report_progress was called the expected number of times to enforce
progress-contract checks: after awaiting make_request_with_periodic_progress
(which uses make_blockscout_request and the shared CreditSink), assert
mock_ctx.report_progress.call_count (or specific call_args if needed) matches
the expected calls (e.g., at least one or a specific count based on
total_duration_hint); locate the test function
test_cross_task_visibility_via_periodic_progress and update its teardown
assertions to include mock_ctx.report_progress assertions so progress reporting
regressions are caught.

Source: Coding guidelines

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Surface low PRO API credit balance via advisory note

1 participant