Skip to content

feat: make transport breaker keys configurable - #151

Merged
bagowix merged 3 commits into
mainfrom
feat/137-configurable-breaker-key
Aug 7, 2026
Merged

feat: make transport breaker keys configurable#151
bagowix merged 3 commits into
mainfrom
feat/137-configurable-breaker-key

Conversation

@bagowix

@bagowix bagowix commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add a typed name_resolver callback to the httpx2/httpx transports, aiohttp middleware, and requests adapter.
  • Keep host-based keys as the default while rejecting empty custom keys before registry access or network I/O.
  • Cover host collapsing, path-based splitting, listener-name alignment, both runtimes, and all supported integrations; document the new option and regenerate the LLM mirror.

Checklist

  • Tests added or updated (suite stays at 100% coverage)
  • uv run ruff format --check and uv run ruff check pass
  • uv run mypy, uv run pyright and uv run pyrefly check pass
  • Docs updated (docs/) for user-facing changes
  • CHANGELOG.md [Unreleased] updated
  • Commits follow Conventional Commits

Related issues

Closes #137

Added

  • Added typed name_resolver callbacks to HTTPX, HTTPX2, aiohttp, and requests integrations.
  • Added documentation for custom breaker names and resolved-name registry sharing.

Fixed

  • Reject empty, whitespace-only, or non-string resolver results with ValueError before registry access or network I/O.
  • Include the request URL in invalid-name errors.
  • Use resolved names for registry keys, open-circuit errors, and listener events.
  • Preserve host-less URL error behavior.

Changed

  • Preserve host-based breaker naming by default.
  • Support grouping requests across hosts or separating requests by path through name_resolver.
  • Expanded the public API of CircuitBreakerTransport, AsyncCircuitBreakerTransport, CircuitBreakerMiddleware, and CircuitBreakerAdapter.
  • Updated CHANGELOG.md and regenerated docs/llms-full.txt.

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 24 untouched benchmarks


Comparing feat/137-configurable-breaker-key (64a9a5b) with main (03d610d)

Open in CodSpeed

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ffebb69-753f-482a-bab9-ed3ca10ed506

📥 Commits

Reviewing files that changed from the base of the PR and between ec695be and 64a9a5b.

📒 Files selected for processing (1)
  • tests/test_httpx.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_httpx.py
📜 Recent review details
⏰ Context from checks skipped due to timeout. (10)
  • GitHub Check: github-advanced-security
  • GitHub Check: quality (3.12)
  • GitHub Check: quality (3.13)
  • GitHub Check: quality (3.14t)
  • GitHub Check: quality (3.11)
  • GitHub Check: quality (3.14)
  • GitHub Check: Coverage
  • GitHub Check: Run benchmarks
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (actions)

Walkthrough

The transport integrations now accept name_resolver callbacks. Resolved names key registries and identify errors and listener events. Host-based resolution remains the default. Blank names fail before network I/O.

Changes

Breaker-name resolution

Layer / File(s) Summary
HTTPX resolver integration
interlock/integrations/httpx.py, interlock/integrations/httpx2.py, tests/test_httpx.py, tests/test_httpx2.py, docs/integrations/httpx.md, docs/integrations/httpx2.md
Synchronous and asynchronous transports accept name_resolver, validate non-blank results, and use resolved names for breaker lookup. Tests cover shared names, path-based separation, listener names, and pre-I/O validation.
aiohttp and requests resolver integration
interlock/integrations/aiohttp.py, interlock/integrations/requests.py, tests/test_aiohttp.py, tests/test_requests.py, docs/integrations/aiohttp.md, docs/integrations/requests.md
Middleware and adapters resolve breaker names from native requests. Blank or non-string results raise ValueError before handler or transport execution.
Reference documentation and release notes
docs/llms-full.txt, CHANGELOG.md
Reference documentation and the unreleased changelog describe custom resolver behavior and resolved-name registry sharing.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant TransportIntegration
  participant name_resolver
  participant Registry
  participant Network
  Client->>TransportIntegration: submit request
  TransportIntegration->>name_resolver: resolve breaker name
  name_resolver-->>TransportIntegration: return name
  TransportIntegration->>Registry: select breaker by resolved name
  TransportIntegration->>Network: perform request after admission
Loading

Possibly related PRs

  • bagowix/interlock#149: Both changes modify transport constructors and registry-backed breaker selection.

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 9
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title uses the required Conventional Commits format, valid type, imperative lowercase summary, no period, and 46 characters.
Linked Issues check ✅ Passed The changes implement configurable resolver-based keys, validation, consistent naming, tests, documentation, changelog, and generated LLM updates required by [#137].
Out of Scope Changes check ✅ Passed All code, tests, documentation, changelog, and generated mirror changes directly support the linked issue objectives.
Zero-Dependency Core ✅ Passed PR adds no core files or core import lines; pyproject.toml keeps dependencies = []; interlock/init.py has no integration re-exports. Existing tenacity imports predate the PR.
Changelog Entry ✅ Passed CHANGELOG.md contains a user-visible-effect bullet under ## [Unreleased] describing name_resolver support and validation.
Docs And Llm Mirror ✅ Passed Public integrations changed; all four relevant docs pages document name_resolver, and docs/llms-full.txt contains matching sections. No new docs page was added.
Tests Accompany Behaviour Change ✅ Passed The PR changes four interlock Python integration files and adds tests in all four corresponding tests files, including new name_resolver cases.
Public Api Surface ✅ Passed The base-to-PR diff has no changes to interlock/init.py or pipeline.py; exported names and public signatures match origin/main.
✨ 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 feat/137-configurable-breaker-key

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 `@interlock/integrations/httpx.py`:
- Around line 95-100: Update _breaker_name in interlock/integrations/httpx.py at
lines 95-100 to validate isinstance(name, str) before calling strip(), raising
ValueError for non-string resolver results; add regression coverage for this
integration. Apply the same validation and regression coverage in
interlock/integrations/httpx2.py at lines 94-100, ensuring bytes and None fail
fast with ValueError.

In `@interlock/integrations/requests.py`:
- Around line 89-91: Update the request URL handling before host extraction:
check request.url explicitly for None and raise the existing ValueError before
calling urlsplit, then pass the validated URL directly to urlsplit without using
an empty-string fallback. Preserve the subsequent no-host validation behavior.
🪄 Autofix

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: CHILL

Plan: Pro Plus

Run ID: 927c7b9e-4ba6-4176-be1c-c81d2a0a7205

📥 Commits

Reviewing files that changed from the base of the PR and between 03d610d and 2a94ca6.

📒 Files selected for processing (14)
  • CHANGELOG.md
  • docs/integrations/aiohttp.md
  • docs/integrations/httpx.md
  • docs/integrations/httpx2.md
  • docs/integrations/requests.md
  • docs/llms-full.txt
  • interlock/integrations/aiohttp.py
  • interlock/integrations/httpx.py
  • interlock/integrations/httpx2.py
  • interlock/integrations/requests.py
  • tests/test_aiohttp.py
  • tests/test_httpx.py
  • tests/test_httpx2.py
  • tests/test_requests.py
📜 Review details
⚠️ CI failures not shown inline (2)

GitHub Actions: Code scanning AI findings on PR #151 / github-advanced-security: Code scanning AI findings on PR #151

Conclusion: failure

View job details

##[group]Run set -euo pipefail
 �[36;1mset -euo pipefail�[0m
 �[36;1mecho "RUNNER_TEMP=$RUNNER_TEMP"�[0m
 �[36;1mfind "$RUNNER_TEMP" -maxdepth 1 -type f -name 'git-credentials-*.config' -print -delete�[0m
 �[36;1m�[0m
 �[36;1m# Generate a unique token and stop processing workflow commands to prevent the runtime from injecting commands�[0m
 �[36;1mSTOP_***REDACTED_SECRET_ASSIGNMENT*** /proc/sys/kernel/random/uuid)�[0m
 �[36;1m�[0m
 �[36;1m# Use a trap to ensure we always resume command processing and check for�[0m
 �[36;1m# fallback error annotations, even if the runtime exits with a non-zero code�[0m
 �[36;1m# (which would otherwise cause set -e to abort the shell before we get here).�[0m
 �[36;1m# The trap preserves the original exit code.�[0m
 �[36;1mcopilot_cleanup() {�[0m
 �[36;1m  �[0m
 �[36;1m  if [ -n "${GIT_PROXY_PID:-}" ] && kill -0 "$GIT_PROXY_PID" 2>/dev/null; then�[0m
 �[36;1m    echo "Stopping git-proxy (pid=$GIT_PROXY_PID)..."�[0m
 �[36;1m    kill "$GIT_PROXY_PID" 2>/dev/null || true�[0m
 �[36;1m    for _ in {1..25}; do�[0m
 �[36;1m      if ! kill -0 "$GIT_PROXY_PID" 2>/dev/null; then break; fi�[0m
 �[36;1m      sleep 0.2�[0m
 �[36;1m    done�[0m
 �[36;1m    if kill -0 "$GIT_PROXY_PID" 2>/dev/null; then�[0m
 �[36;1m      echo "git-proxy did not stop gracefully; forcing termination."�[0m
 �[36;1m      kill -KILL "$GIT_PROXY_PID" 2>/dev/null || true�[0m
 �[36;1m    fi�[0m
 �[36;1m    wait "$GIT_PROXY_PID" 2>/dev/null || true�[0m
 �[36;1m  fi�[0m
 �[36;1m  �[0m
 �[36;1m  echo "::$STOP_***REDACTED_SECRET_ASSIGNMENT***
 �[36;1m  FALLBACK_FILE="${RUNNER_TEMP}/copilot-fallback-error.txt"�[0m
 �[36;1m  if [ -f "$FALLBACK_FILE" ]; then�[0m
 �[36;1m    FALLBACK_MSG=$(head -c 500 "$FALLBACK_FILE" | tr -d '\n\r')�[0m
 �[36;1m    echo "::error title=Copilot Error::${FALLBACK_MSG}"�[0m

GitHub Actions: Code scanning AI findings on PR #151 / 0_github-advanced-security.txt: Code scanning AI findings on PR #151

Conclusion: failure

View job details

##[group]Run set -euo pipefail
 �[36;1mset -euo pipefail�[0m
 �[36;1mecho "RUNNER_TEMP=$RUNNER_TEMP"�[0m
 �[36;1mfind "$RUNNER_TEMP" -maxdepth 1 -type f -name 'git-credentials-*.config' -print -delete�[0m
 �[36;1m�[0m
 �[36;1m# Generate a unique token and stop processing workflow commands to prevent the runtime from injecting commands�[0m
 �[36;1mSTOP_***REDACTED_SECRET_ASSIGNMENT*** /proc/sys/kernel/random/uuid)�[0m
 �[36;1m�[0m
 �[36;1m# Use a trap to ensure we always resume command processing and check for�[0m
 �[36;1m# fallback error annotations, even if the runtime exits with a non-zero code�[0m
 �[36;1m# (which would otherwise cause set -e to abort the shell before we get here).�[0m
 �[36;1m# The trap preserves the original exit code.�[0m
 �[36;1mcopilot_cleanup() {�[0m
 �[36;1m  �[0m
 �[36;1m  if [ -n "${GIT_PROXY_PID:-}" ] && kill -0 "$GIT_PROXY_PID" 2>/dev/null; then�[0m
 �[36;1m    echo "Stopping git-proxy (pid=$GIT_PROXY_PID)..."�[0m
 �[36;1m    kill "$GIT_PROXY_PID" 2>/dev/null || true�[0m
 �[36;1m    for _ in {1..25}; do�[0m
 �[36;1m      if ! kill -0 "$GIT_PROXY_PID" 2>/dev/null; then break; fi�[0m
 �[36;1m      sleep 0.2�[0m
 �[36;1m    done�[0m
 �[36;1m    if kill -0 "$GIT_PROXY_PID" 2>/dev/null; then�[0m
 �[36;1m      echo "git-proxy did not stop gracefully; forcing termination."�[0m
 �[36;1m      kill -KILL "$GIT_PROXY_PID" 2>/dev/null || true�[0m
 �[36;1m    fi�[0m
 �[36;1m    wait "$GIT_PROXY_PID" 2>/dev/null || true�[0m
 �[36;1m  fi�[0m
 �[36;1m  �[0m
 �[36;1m  echo "::$STOP_***REDACTED_SECRET_ASSIGNMENT***
 �[36;1m  FALLBACK_FILE="${RUNNER_TEMP}/copilot-fallback-error.txt"�[0m
 �[36;1m  if [ -f "$FALLBACK_FILE" ]; then�[0m
 �[36;1m    FALLBACK_MSG=$(head -c 500 "$FALLBACK_FILE" | tr -d '\n\r')�[0m
 �[36;1m    echo "::error title=Copilot Error::${FALLBACK_MSG}"�[0m
🧰 Additional context used
📓 Path-based instructions (10)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Support Python 3.11 and newer; use Python 3.11+ features where required.
Keep the core zero-dependency and use only the standard library; external dependencies must be isolated behind optional integrations.
Use injected Clock instances for all time access; do not call time.monotonic() or sleep() directly in logic.
Implement the core as an I/O-free state machine with a single threading.Lock around the await-free critical section, never held across the protected call.
Use Protocols for extension points: Clock, SlidingWindow, Storage, FailureClassifier, and EventListener; do not inherit from internal classes.
Expose one public CircuitBreaker class for sync and async operation, with separate internal paths selected by coroutine detection.
Expose the public API through the package __init__.py; keep helpers underscore-prefixed and hidden.
Use absolute imports, placed at the top of the file, ordered as standard library, third-party, then local imports with blank lines between groups.
Use a maximum line length of 100 characters, single-quoted strings, f-strings, and pathlib.Path instead of os.path.
Annotate every parameter and return value; use modern generic syntax and X | None instead of Optional[X].
Use StrEnum or module-level constants instead of magic constants.
When a constructor or function has three or more arguments, pass them by keyword.
Keep functions focused on one job, generally no longer than 20–30 lines, with minimal side effects and extracted repeated loop logic.
Use async/await for I/O-bound work, asyncio.TaskGroup instead of asyncio.gather, and asyncio.to_thread or ProcessPoolExecutor for CPU-bound work.
Do not mix sync and async in one function; never await a sync callable or block on an async callable.
Fail fast on invalid input or state by raising immediately; do not continue with partial results or invented defaults.
Catch only expected exceptions, log them with context, and re-raise; do not use ...

Files:

  • tests/test_aiohttp.py
  • tests/test_requests.py
  • tests/test_httpx2.py
  • tests/test_httpx.py
  • interlock/integrations/aiohttp.py
  • interlock/integrations/httpx.py
  • interlock/integrations/requests.py
  • interlock/integrations/httpx2.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.py: Use pytest functions rather than test classes, with names formatted as test__unit_of_work__state_under_test__expected_behavior.
Mirror package layout in test filenames, use Arrange-Act-Assert, and create fixtures for repeated setup.
Use injected Clock instances for deterministic tests; do not use sleep() in tests.
Use pytest-asyncio and @pytest.mark.asyncio for asynchronous tests, and use pytest-mock to isolate external dependencies.
Use Hypothesis property-based tests for the state machine and cover all transitions and races.
Write the reproducing test before a bug fix and specify the required behavior before implementing a feature.

Tests must preserve 100% coverage, avoid sleep for time-dependent behavior, and use injected clocks instead.

Files:

  • tests/test_aiohttp.py
  • tests/test_requests.py
  • tests/test_httpx2.py
  • tests/test_httpx.py

⚙️ CodeRabbit configuration file

pytest functions only, never test classes. Names follow test__unit_of_work__state_under_test__expected_behavior in lower case. One behaviour per test, Arrange-Act-Assert. Time is the injected fake Clock — any real sleep or wall-clock read is flakiness, flag it. Async tests use @pytest.mark.asyncio; state-machine work carries hypothesis property tests. Coverage must stay at 100%: point out uncovered branches the diff introduces. Tests run under -n auto, so anything relying on ordering or shared global state is a bug.

Files:

  • tests/test_aiohttp.py
  • tests/test_requests.py
  • tests/test_httpx2.py
  • tests/test_httpx.py
**/*.md

📄 CodeRabbit inference engine (AGENTS.md)

Document user-facing changes in English Markdown documentation and keep generated documentation mirrors synchronized.

Files:

  • docs/integrations/requests.md
  • CHANGELOG.md
  • docs/integrations/httpx.md
  • docs/integrations/aiohttp.md
  • docs/integrations/httpx2.md
{interlock/**/*.py,docs/**/*.md,docs/llms-full.txt,docs/llms.txt}

📄 CodeRabbit inference engine (Custom checks)

When a change affects user-facing behaviour through the public API, integrations, or configuration options, update the relevant page under docs/ and regenerate docs/llms-full.txt; when adding a new documentation page, list it under ## Docs in docs/llms.txt.

Files:

  • docs/integrations/requests.md
  • docs/integrations/httpx.md
  • docs/integrations/aiohttp.md
  • docs/integrations/httpx2.md
  • interlock/integrations/aiohttp.py
  • interlock/integrations/httpx.py
  • docs/llms-full.txt
  • interlock/integrations/requests.py
  • interlock/integrations/httpx2.py
docs/**/*.{md,mdx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

User-facing changes must update the relevant documentation page under docs/.

Files:

  • docs/integrations/requests.md
  • docs/integrations/httpx.md
  • docs/integrations/aiohttp.md
  • docs/integrations/httpx2.md
docs/**/*.md

⚙️ CodeRabbit configuration file

User-facing documentation. Check that code samples match the current public API and would actually run. A new page must also be listed in docs/llms.txt under ## Docs. Keep the existing voice: short sentences, no marketing.

Files:

  • docs/integrations/requests.md
  • docs/integrations/httpx.md
  • docs/integrations/aiohttp.md
  • docs/integrations/httpx2.md
CHANGELOG.md

📄 CodeRabbit inference engine (AGENTS.md)

Add every change to the [Unreleased] section under Added, Fixed, or Changed, explaining user impact rather than only symbol movement.

User-facing changes must update the [Unreleased] section.

Files:

  • CHANGELOG.md

⚙️ CodeRabbit configuration file

Keep a Changelog format. New entries go under ## [Unreleased] in Added / Fixed / Changed. An entry describes what a user could not do before and can now, not which symbol moved. Only the release commit dates a section and updates the link references.

Files:

  • CHANGELOG.md
interlock/**/*.py

📄 CodeRabbit inference engine (Custom checks)

Every production behaviour change in interlock/ must be accompanied by a change under tests/; changes limited to docstrings, comments, or type annotations are exempt. Bug fixes must include at least one regression test that fails without the production fix.

Keep the core dependency-free; external dependencies must belong to extras and be imported lazily.

Files:

  • interlock/integrations/aiohttp.py
  • interlock/integrations/httpx.py
  • interlock/integrations/requests.py
  • interlock/integrations/httpx2.py

⚙️ CodeRabbit configuration file

Core rules (AGENTS.md is authoritative): (1) Zero-dependency core — anything under interlock/ except interlock/integrations/ may import stdlib only. Flag every third-party import as a blocking issue. (2) No fallbacks, no silent excepts, no a or b or c for required config or data, no hidden retries. Invalid input or state raises immediately. interlock/_notify.py is the one sanctioned swallow (listener hooks are observability, logged with traceback, BaseException still propagates) — do not suggest generalising or "fixing" it. (3) Time comes only from the injected Clock protocol; direct time.monotonic()/time.sleep() in library logic is a bug. (4) Style: 100-char lines, single quotes, f-strings, pathlib, full annotations, X | None never Optional[X], keyword arguments for calls with 3+ arguments, no magic constants (StrEnum or module constants), functions under ~30 lines. (5) Extension points are Protocols (Clock, SlidingWindow, Storage, FailureClassifier, EventListener) — do not propose inheriting internal classes. (6) Sync and async live in one CircuitBreaker with separate internal paths; never propose Sync*/Async* twins and never mix the two paths in one function. (7) Public API is exported from interlock/init.py; everything else is underscore-prefixed. New public symbols need __all__ and a docstring. (8) Python 3.11 is the floor — no 3.12+ syntax or stdlib.

Files:

  • interlock/integrations/aiohttp.py
  • interlock/integrations/httpx.py
  • interlock/integrations/requests.py
  • interlock/integrations/httpx2.py
interlock/integrations/**/*.py

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Integration dependencies must be optional extras and imported lazily.

Files:

  • interlock/integrations/aiohttp.py
  • interlock/integrations/httpx.py
  • interlock/integrations/requests.py
  • interlock/integrations/httpx2.py

⚙️ CodeRabbit configuration file

Optional extras. The third-party import must stay inside this package, must never be re-exported from interlock/init.py, and a missing extra must fail with a clear install hint rather than a fallback. Wrap the dependency behind the project's own types so its objects do not leak into core signatures. Check that the extra is declared in pyproject.toml [project.optional-dependencies] and documented under docs/integrations/.

Files:

  • interlock/integrations/aiohttp.py
  • interlock/integrations/httpx.py
  • interlock/integrations/requests.py
  • interlock/integrations/httpx2.py
docs/llms-full.txt

⚙️ CodeRabbit configuration file

Generated artefact — produced by uv run python scripts/build_llms_full.py. Do not review its content or suggest edits; only confirm it was regenerated together with the docs/ changes in the same PR.

Files:

  • docs/llms-full.txt

Comment thread interlock/integrations/httpx.py
Comment thread interlock/integrations/requests.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 `@tests/test_httpx.py`:
- Around line 313-317: Update both invalid-name tests around
transport.handle_request to spy on registry.get and assert it is never called
after the ValueError. Keep the existing exception-message and inner.calls
assertions, ensuring validation rejects the breaker name before any registry
lookup or breaker creation.
🪄 Autofix

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: CHILL

Plan: Pro Plus

Run ID: 61909767-9e9d-4680-a4cf-0106d24ee269

📥 Commits

Reviewing files that changed from the base of the PR and between 2a94ca6 and ec695be.

📒 Files selected for processing (14)
  • CHANGELOG.md
  • docs/integrations/aiohttp.md
  • docs/integrations/httpx.md
  • docs/integrations/httpx2.md
  • docs/integrations/requests.md
  • docs/llms-full.txt
  • interlock/integrations/aiohttp.py
  • interlock/integrations/httpx.py
  • interlock/integrations/httpx2.py
  • interlock/integrations/requests.py
  • tests/test_aiohttp.py
  • tests/test_httpx.py
  • tests/test_httpx2.py
  • tests/test_requests.py
🚧 Files skipped from review as they are similar to previous changes (13)
  • docs/integrations/requests.md
  • CHANGELOG.md
  • tests/test_httpx2.py
  • tests/test_requests.py
  • docs/integrations/httpx2.md
  • docs/llms-full.txt
  • docs/integrations/aiohttp.md
  • interlock/integrations/httpx2.py
  • interlock/integrations/aiohttp.py
  • tests/test_aiohttp.py
  • interlock/integrations/httpx.py
  • docs/integrations/httpx.md
  • interlock/integrations/requests.py
📜 Review details
⏰ Context from checks skipped due to timeout. (7)
  • GitHub Check: quality (3.14t)
  • GitHub Check: quality (3.14)
  • GitHub Check: quality (3.12)
  • GitHub Check: quality (3.13)
  • GitHub Check: quality (3.11)
  • GitHub Check: Coverage
  • GitHub Check: Run benchmarks
⚠️ CI failures not shown inline (2)

GitHub Actions: Code scanning AI findings on PR #151 / github-advanced-security: Code scanning AI findings on PR #151

Conclusion: failure

View job details

##[group]Run set -euo pipefail
 �[36;1mset -euo pipefail�[0m
 �[36;1mecho "RUNNER_TEMP=$RUNNER_TEMP"�[0m
 �[36;1mfind "$RUNNER_TEMP" -maxdepth 1 -type f -name 'git-credentials-*.config' -print -delete�[0m
 �[36;1m�[0m
 �[36;1m# Generate a unique token and stop processing workflow commands to prevent the runtime from injecting commands�[0m
 �[36;1mSTOP_***REDACTED_SECRET_ASSIGNMENT*** /proc/sys/kernel/random/uuid)�[0m
 �[36;1m�[0m
 �[36;1m# Use a trap to ensure we always resume command processing and check for�[0m
 �[36;1m# fallback error annotations, even if the runtime exits with a non-zero code�[0m
 �[36;1m# (which would otherwise cause set -e to abort the shell before we get here).�[0m
 �[36;1m# The trap preserves the original exit code.�[0m
 �[36;1mcopilot_cleanup() {�[0m
 �[36;1m  �[0m
 �[36;1m  if [ -n "${GIT_PROXY_PID:-}" ] && kill -0 "$GIT_PROXY_PID" 2>/dev/null; then�[0m
 �[36;1m    echo "Stopping git-proxy (pid=$GIT_PROXY_PID)..."�[0m
 �[36;1m    kill "$GIT_PROXY_PID" 2>/dev/null || true�[0m
 �[36;1m    for _ in {1..25}; do�[0m
 �[36;1m      if ! kill -0 "$GIT_PROXY_PID" 2>/dev/null; then break; fi�[0m
 �[36;1m      sleep 0.2�[0m
 �[36;1m    done�[0m
 �[36;1m    if kill -0 "$GIT_PROXY_PID" 2>/dev/null; then�[0m
 �[36;1m      echo "git-proxy did not stop gracefully; forcing termination."�[0m
 �[36;1m      kill -KILL "$GIT_PROXY_PID" 2>/dev/null || true�[0m
 �[36;1m    fi�[0m
 �[36;1m    wait "$GIT_PROXY_PID" 2>/dev/null || true�[0m
 �[36;1m  fi�[0m
 �[36;1m  �[0m
 �[36;1m  echo "::$STOP_***REDACTED_SECRET_ASSIGNMENT***
 �[36;1m  FALLBACK_FILE="${RUNNER_TEMP}/copilot-fallback-error.txt"�[0m
 �[36;1m  if [ -f "$FALLBACK_FILE" ]; then�[0m
 �[36;1m    FALLBACK_MSG=$(head -c 500 "$FALLBACK_FILE" | tr -d '\n\r')�[0m
 �[36;1m    echo "::error title=Copilot Error::${FALLBACK_MSG}"�[0m

GitHub Actions: Code scanning AI findings on PR #151 / 0_github-advanced-security.txt: Code scanning AI findings on PR #151

Conclusion: failure

View job details

##[group]Run set -euo pipefail
 �[36;1mset -euo pipefail�[0m
 �[36;1mecho "RUNNER_TEMP=$RUNNER_TEMP"�[0m
 �[36;1mfind "$RUNNER_TEMP" -maxdepth 1 -type f -name 'git-credentials-*.config' -print -delete�[0m
 �[36;1m�[0m
 �[36;1m# Generate a unique token and stop processing workflow commands to prevent the runtime from injecting commands�[0m
 �[36;1mSTOP_***REDACTED_SECRET_ASSIGNMENT*** /proc/sys/kernel/random/uuid)�[0m
 �[36;1m�[0m
 �[36;1m# Use a trap to ensure we always resume command processing and check for�[0m
 �[36;1m# fallback error annotations, even if the runtime exits with a non-zero code�[0m
 �[36;1m# (which would otherwise cause set -e to abort the shell before we get here).�[0m
 �[36;1m# The trap preserves the original exit code.�[0m
 �[36;1mcopilot_cleanup() {�[0m
 �[36;1m  �[0m
 �[36;1m  if [ -n "${GIT_PROXY_PID:-}" ] && kill -0 "$GIT_PROXY_PID" 2>/dev/null; then�[0m
 �[36;1m    echo "Stopping git-proxy (pid=$GIT_PROXY_PID)..."�[0m
 �[36;1m    kill "$GIT_PROXY_PID" 2>/dev/null || true�[0m
 �[36;1m    for _ in {1..25}; do�[0m
 �[36;1m      if ! kill -0 "$GIT_PROXY_PID" 2>/dev/null; then break; fi�[0m
 �[36;1m      sleep 0.2�[0m
 �[36;1m    done�[0m
 �[36;1m    if kill -0 "$GIT_PROXY_PID" 2>/dev/null; then�[0m
 �[36;1m      echo "git-proxy did not stop gracefully; forcing termination."�[0m
 �[36;1m      kill -KILL "$GIT_PROXY_PID" 2>/dev/null || true�[0m
 �[36;1m    fi�[0m
 �[36;1m    wait "$GIT_PROXY_PID" 2>/dev/null || true�[0m
 �[36;1m  fi�[0m
 �[36;1m  �[0m
 �[36;1m  echo "::$STOP_***REDACTED_SECRET_ASSIGNMENT***
 �[36;1m  FALLBACK_FILE="${RUNNER_TEMP}/copilot-fallback-error.txt"�[0m
 �[36;1m  if [ -f "$FALLBACK_FILE" ]; then�[0m
 �[36;1m    FALLBACK_MSG=$(head -c 500 "$FALLBACK_FILE" | tr -d '\n\r')�[0m
 �[36;1m    echo "::error title=Copilot Error::${FALLBACK_MSG}"�[0m
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Support Python 3.11 and newer; use Python 3.11+ features where required.
Keep the core zero-dependency and use only the standard library; external dependencies must be isolated behind optional integrations.
Use injected Clock instances for all time access; do not call time.monotonic() or sleep() directly in logic.
Implement the core as an I/O-free state machine with a single threading.Lock around the await-free critical section, never held across the protected call.
Use Protocols for extension points: Clock, SlidingWindow, Storage, FailureClassifier, and EventListener; do not inherit from internal classes.
Expose one public CircuitBreaker class for sync and async operation, with separate internal paths selected by coroutine detection.
Expose the public API through the package __init__.py; keep helpers underscore-prefixed and hidden.
Use absolute imports, placed at the top of the file, ordered as standard library, third-party, then local imports with blank lines between groups.
Use a maximum line length of 100 characters, single-quoted strings, f-strings, and pathlib.Path instead of os.path.
Annotate every parameter and return value; use modern generic syntax and X | None instead of Optional[X].
Use StrEnum or module-level constants instead of magic constants.
When a constructor or function has three or more arguments, pass them by keyword.
Keep functions focused on one job, generally no longer than 20–30 lines, with minimal side effects and extracted repeated loop logic.
Use async/await for I/O-bound work, asyncio.TaskGroup instead of asyncio.gather, and asyncio.to_thread or ProcessPoolExecutor for CPU-bound work.
Do not mix sync and async in one function; never await a sync callable or block on an async callable.
Fail fast on invalid input or state by raising immediately; do not continue with partial results or invented defaults.
Catch only expected exceptions, log them with context, and re-raise; do not use ...

Files:

  • tests/test_httpx.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.py: Use pytest functions rather than test classes, with names formatted as test__unit_of_work__state_under_test__expected_behavior.
Mirror package layout in test filenames, use Arrange-Act-Assert, and create fixtures for repeated setup.
Use injected Clock instances for deterministic tests; do not use sleep() in tests.
Use pytest-asyncio and @pytest.mark.asyncio for asynchronous tests, and use pytest-mock to isolate external dependencies.
Use Hypothesis property-based tests for the state machine and cover all transitions and races.
Write the reproducing test before a bug fix and specify the required behavior before implementing a feature.

Tests must preserve 100% coverage, avoid sleep for time-dependent behavior, and use injected clocks instead.

Files:

  • tests/test_httpx.py

⚙️ CodeRabbit configuration file

pytest functions only, never test classes. Names follow test__unit_of_work__state_under_test__expected_behavior in lower case. One behaviour per test, Arrange-Act-Assert. Time is the injected fake Clock — any real sleep or wall-clock read is flakiness, flag it. Async tests use @pytest.mark.asyncio; state-machine work carries hypothesis property tests. Coverage must stay at 100%: point out uncovered branches the diff introduces. Tests run under -n auto, so anything relying on ordering or shared global state is a bug.

Files:

  • tests/test_httpx.py

Comment thread tests/test_httpx.py
@bagowix
bagowix merged commit 114bee5 into main Aug 7, 2026
16 of 17 checks passed
@bagowix
bagowix deleted the feat/137-configurable-breaker-key branch August 7, 2026 21:47
@bagowix bagowix mentioned this pull request Aug 7, 2026
6 tasks
bagowix added a commit that referenced this pull request Aug 7, 2026
## Summary

Prepare the `2.5.0` minor release.

- Bump the package version from `2.4.0` to `2.5.0`.
- Move the current `[Unreleased]` changelog entries into `[2.5.0] -
2026-08-08`.
- Update changelog comparison links and the comparison-page release
version.
- Regenerate `docs/llms-full.txt`.

Minor, not patch: the release includes backward-compatible public
additions: configurable transport breaker names, shared caller-owned
registries, and narrower listener protocols.

## Checklist

- [x] Tests added or updated (suite stays at 100% coverage)
- [x] `uv run ruff format --check` and `uv run ruff check` pass
- [x] `uv run mypy`, `uv run pyright` and `uv run pyrefly check` pass
- [x] Docs updated (`docs/`) for user-facing changes
- [x] `CHANGELOG.md` `[Unreleased]` updated
- [x] Commits follow Conventional Commits

Additional release checks: package and strict documentation builds pass.
Griffe reports only the expected public `VERSION` change (`2.4.0` →
`2.5.0`).

## Related issues

Closes #148
Closes #149
Closes #150
Closes #151
Closes #152
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.

Make the breaker key configurable in the transport-level integrations

1 participant