Repository navigation
fix(agent-gateway): add retry strategy - #433
Draft
betinacosta wants to merge 2 commits into
Draft
betinacosta wants to merge 2 commits into
betinacosta wants to merge 2 commits into
Conversation
betinacosta
commented
Oct 28, 2026
betinacosta
left a comment
Member
Author
There was a problem hiding this comment.
PR Review: #433: fix(agent-gateway): add retry strategy
Author: betinacosta Branch: fix/agw-user-auth-retry → main
Verdict:
Summary: Solid, well-tested retry implementation with two minor issues — an unfilled PR template and a non-asserting call count check in the test suite.
A: Process & Compliance
| # | Criterion | Status | Finding |
|---|---|---|---|
| A1 | PR template complete | ❌ | PR body still has the raw template with no checkboxes ticked, no description of the change, no actual issue linked, no test steps, and "Type of Change" unchecked. |
| A2 | Conventional Commits | ✅ | PR title fix(agent-gateway): add retry strategy is valid conventional-commits format; commit-validation CI job passes. |
| A3 | Issue linked | Body still contains the placeholder Closes #<issue_number> — no real issue number provided. |
|
| A4 | AI-generated code disclosure | ➖ | Code does not appear AI-generated in style or structure. |
B: Security & Sensitive Data
| # | Criterion | Status | Finding |
|---|---|---|---|
| B1 | No sensitive data in code | ✅ | No credentials, tokens, internal URLs, tenant IDs, or customer data in the diff. |
| B2 | No sensitive data in PR body | ✅ | PR body contains only the unfilled template placeholder text — nothing sensitive. |
C: Code Quality
| # | Criterion | Status | Finding |
|---|---|---|---|
| C1 | CI checks passing | Integration Tests is still pending; all other required jobs pass (Code Quality, Tests & Coverage, Build & Package, Commit Validation, Check Version Bump, Analyze (python)). "SDK review label present" is a gate check that will clear once this review completes. |
|
| C2 | Version bump | ➖ | No manual version bump in pyproject.toml. The uv.lock change bumps ruff (dev tool) and widens setuptools upper bound — both are legitimate lock-file drift, not a version-bump violation. |
| C3 | Type hints | ✅ | _fetch_auth_token already had full annotations; the retry refactor preserves them. `last_exc: Exception |
| C4 | No hardcoded values | ✅ | Magic numbers extracted into module-level constants _FETCH_AUTH_MAX_ATTEMPTS = 3 and _FETCH_AUTH_BACKOFF_BASE = 1.0 at _lob.py:66-67. |
| C5 | Import organization | ✅ | import time added at the top-level alongside existing stdlib imports (_lob.py:11). No lazy imports introduced. |
| C6 | Naming conventions | ✅ | New constants follow SCREAMING_SNAKE_CASE; no new enums or files. |
| C7 | No unused code | ✅ | time import is used at _lob.py:169. No dead code introduced. |
| C8 | No unjustified new dependencies | ✅ | time is stdlib. The ruff and setuptools changes in uv.lock are minor version bumps, not new runtime deps. |
| C9 | Proto code freshness | ➖ | No proto files changed. |
D: API & Design
| # | Criterion | Status | Finding |
|---|---|---|---|
| D1 | API future-proofing | ✅ | Constants allow future callers to adjust retry policy; no new public API surface introduced. |
| D2 | Public API hygiene | ✅ | _fetch_auth_token is a private helper (leading underscore); no public __all__ changes. |
| D3 | Breaking changes marked | ➖ | No breaking changes. |
| D4 | Pagination & tenant filtering | ➖ | Not applicable to this change. |
| D5 | Telemetry instrumentation | ➖ | No new client methods or module added. |
| D6 | Multi-tenancy support | ➖ | Retry logic is transparent to the multi-tenancy layer already handled by _fetch_auth_token's tenant_subdomain param. |
E: Tests & Documentation
| # | Criterion | Status | Finding |
|---|---|---|---|
| E1 | Tests added/updated | ✅ | New TestFetchAuthTokenRetry class with 6 tests covering: success on first attempt, transient error → retry → success, full exhaustion, MCPServerNotFoundError bypass, backoff sequence, and exact delay values. time.sleep is patched to avoid real delays. |
| E2 | Documentation quality | ✅ | Docstring for _fetch_auth_token updated with retry semantics (_lob.py:108-123). |
| E2a | Multi-tenancy documentation | ➖ | Not a new module; no user-guide changes required. |
| E3 | Module structure compliance | ➖ | No new module added. |
❌ Blocking Issues
- [A1]: PR body is the unfilled template — description, type of change, checklist, and test steps are all empty placeholders. Please fill in at minimum: what the bug was and when it occurs, the type of change (Bug fix), and the checklist items.
⚠️ Non-Blocking Suggestions
- [A3]: Replace the
Closes #<issue_number>placeholder with the actual issue number, or remove the line if no issue tracks this bug. - [E1]:
tests/agentgateway/unit/test_lob.py:283—mock_client.return_value.get_destination.call_count == 1is a bare expression, not an assertion. It evaluates toTrueorFalseand is silently discarded. Change it toassert mock_client.return_value.get_destination.call_count == 1to actually verify the no-retry behaviour. - [C1]:
Integration Testsis pending — confirm it passes before merging.
✅ Things Done Well
- The retry logic correctly exempts
MCPServerNotFoundErrorfrom retry (it is a client-side "resource not found", not a transient failure) — this is the right semantic distinction. - Module-level constants (
_FETCH_AUTH_MAX_ATTEMPTS,_FETCH_AUTH_BACKOFF_BASE) with an inline comment documenting the resulting delay sequence (1s, 2s) make the policy easy to audit and change. time.sleepis properly patched in all retry tests, so the test suite runs in milliseconds regardless of the configured backoff.- The
type: ignore[misc]annotation onraise last_excis correct — mypy/ty cannot provelast_excis non-None at that point in the control flow (the loop always executes at least once, so it is safe).
betinacosta
commented
Oct 28, 2026
betinacosta
left a comment
Member
Author
There was a problem hiding this comment.
PR Review: #433: fix(agent-gateway): add retry strategy
Author: betinacosta Branch: fix/agw-user-auth-retry → main
Verdict: ✅ Ready to Merge
Summary: Clean, well-tested retry implementation — all CI passes, PR body is fully filled in, and the previously flagged bare-expression test issue has been corrected.
A: Process & Compliance
| # | Criterion | Status | Finding |
|---|---|---|---|
| A1 | PR template complete | ✅ | Description, Type of Change (Bug fix ✅), How to Test (4 numbered steps), and all 9 checklist items filled in. Two conditional items (new module e2e evidence, security-review) are N/A and not present. |
| A2 | Conventional Commits | ✅ | PR title fix(agent-gateway): add retry strategy is valid conventional-commits format; Commit Validation CI job passes. |
| A3 | Issue linked | No Closes #N in the PR body — the "Related Issue" section is omitted entirely. If a GitHub issue tracks this bug, link it. |
|
| A4 | AI-generated code disclosure | ➖ | Code does not appear AI-generated in style or structure. |
B: Security & Sensitive Data
| # | Criterion | Status | Finding |
|---|---|---|---|
| B1 | No sensitive data in code | ✅ | No credentials, tokens, internal URLs, tenant IDs, or customer data in the diff. |
| B2 | No sensitive data in PR body | ✅ | PR body contains only public implementation details — no sensitive or SAP-internal content. |
C: Code Quality
| # | Criterion | Status | Finding |
|---|---|---|---|
| C1 | CI checks passing | ✅ | All required jobs pass: Code Quality Checks, Tests & Coverage, Build & Package, Commit Validation, Check Version Bump, Analyze (python), Integration Tests. |
| C2 | Version bump | ➖ | No manual version bump in pyproject.toml (still 0.62.0). The uv.lock changes bump ruff from 0.16.0 → 0.17.1 (dev-only) and widen the setuptools upper bound <84 → <85 — both are legitimate lock-file drift, not version-bump violations. |
| C3 | Type hints | ✅ | _fetch_auth_token had full annotations before; the refactor preserves them. New variable last_exc: Exception | None = None at _lob.py:125 is properly annotated. |
| C4 | No hardcoded values | ✅ | Retry policy extracted into module-level constants _FETCH_AUTH_MAX_ATTEMPTS = 3 (_lob.py:66) and _FETCH_AUTH_BACKOFF_BASE = 1.0 (_lob.py:67). |
| C5 | Import organization | ✅ | import time added at the top-level alongside existing stdlib imports (_lob.py:11). No lazy imports. |
| C6 | Naming conventions | ✅ | New constants follow SCREAMING_SNAKE_CASE. No new enums, files, or workflow files. |
| C7 | No unused code | ✅ | time is used at _lob.py:169. No dead code introduced. |
| C8 | No unjustified new dependencies | ✅ | time is stdlib. ruff and setuptools changes in uv.lock are minor version updates to existing dev/build deps — no new runtime dependency added. |
| C9 | Proto code freshness | ➖ | No proto files changed. |
D: API & Design
| # | Criterion | Status | Finding |
|---|---|---|---|
| D1 | API future-proofing | ✅ | Module-level constants make the retry policy easy to audit and change without touching the logic. No new public API surface. |
| D2 | Public API hygiene | ✅ | _fetch_auth_token is a private helper (leading underscore). No __all__ changes. |
| D3 | Breaking changes marked | ➖ | No breaking changes. |
| D4 | Pagination & tenant filtering | ➖ | Not applicable — retry is transparent to callers. |
| D5 | Telemetry instrumentation | ➖ | No new client methods or module added. |
| D6 | Multi-tenancy support | ➖ | Retry logic is transparent to the multi-tenancy layer already handled by _fetch_auth_token's tenant_subdomain param. |
E: Tests & Documentation
| # | Criterion | Status | Finding |
|---|---|---|---|
| E1 | Tests added/updated | ✅ | New TestFetchAuthTokenRetry class (6 tests, test_lob.py:203-298) covers: first-attempt success, transient-error-then-success (1 retry), two failures-then-success (2 retries), full exhaustion, MCPServerNotFoundError bypass (line 283 correctly uses assert mock_client.return_value.get_destination.call_count == 1), and exact exponential delay sequence [1.0, 2.0]. time.sleep is patched in all retry tests. |
| E2 | Documentation quality | ✅ | _fetch_auth_token docstring updated with retry semantics at _lob.py:108-123, including what is and isn't retried and the updated Raises: section. |
| E2a | Multi-tenancy documentation | ➖ | Not a new module; no user-guide changes required. |
| E3 | Module structure compliance | ➖ | No new module added. |
⚠️ Non-Blocking Suggestions
- [A3]: No issue linked — if a GitHub issue tracks this transient-auth bug, add
Closes #Nto the PR body. - [B1 / Security note]:
logger.warning("... %s ...", exc, ...)at_lob.py:163logs the full exception message. BTP Destination Service OAuth2 errors can occasionally embed partial token fragments or tenant identifiers in their error bodies. Consider loggingtype(exc).__name__only, or sanitising the message, to avoid leaking sensitive strings into application logs. This is advisory — it is not a definite information-disclosure risk, just a hardening recommendation.
✅ Things Done Well
MCPServerNotFoundErroris correctly excluded from retries — it signals a genuine missing resource, not a transient failure. This is the semantically correct distinction and prevents silent retry-loops on misconfigured destinations.- Module-level constants (
_FETCH_AUTH_MAX_ATTEMPTS,_FETCH_AUTH_BACKOFF_BASE) with an inline comment documenting the resulting delay sequence make the policy transparent and easy to tune. time.sleepis patched in every test that exercises retry paths, so the suite runs at full speed with no real waits.- The
type: ignore[misc]annotation onraise last_exc(_lob.py:171) is correct — mypy/ty cannot provelast_excis non-None at that point (the loop always executes at least once), so the suppression is justified. - The bare-expression issue flagged in the earlier review (
call_count == 1withoutassert) has been corrected —test_lob.py:283now uses a properassert. - All CI checks pass, including Integration Tests.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
_fetch_auth_token()inagentgateway/_lob.pypreviously failed on the first transient error from the BTP Destination Service — e.g. an OAuth2 token endpoint blip or an unparseable destination response — with no retry. This PR wraps the function body in a retry loop of up to 3 attempts with exponential backoff (1 s, 2 s).MCPServerNotFoundErroris excluded from retries: it signals a genuine missing resource (no destination configured, no auth token returned, or empty auth header), not a transient failure, so it is re-raised immediately.Type of Change
How to Test
pytest tests/agentgateway/unit/test_lob.py::TestFetchAuthTokenRetry -vtime.sleepis never called in production paths (only in retry scenarios).TestFetchAuthTokensuite still passes (no regressions in the non-retry path).Checklist
Additional Notes
Code Review
/prep-pr; confirm checklist items marked[ ]before merging.Closes #<issue_number>with the real issue number, or remove the line if there is no tracked issue.tests/agentgateway/unit/test_lob.py:283:mock_client.return_value.get_destination.call_count == 1is a bare expression (not anassert), so it is silently discarded. Change it toassert mock_client.return_value.get_destination.call_count == 1to actually enforce the no-retry behaviour.Security Review
logger.warning(..., exc, ...)logs the full exception message string. If the BTP Destination Service OAuth2 endpoint returns error responses that include token fragments, tenant identifiers, or other sensitive strings, those may appear in application logs. Consider loggingtype(exc).__name__only, or truncating/sanitising the message before logging.