fix(#6546): add retry-with-backoff for transient errors in GCP client - #6861
Conversation
The GCP IAM API HTTP client (internal/gcp/client.go) lacked retry logic for transient transport errors, causing WIF pool provisioning to fail on connection resets, TLS handshake timeouts, and unexpected EOF — errors that are common under normal operating conditions. Add retry-with-exponential-backoff to DoRequest, the shared HTTP method used by all GCP API callers (WIF provisioning, Secret Manager, Cloud Run, IAM bindings). The retry covers: - Transport errors: TCP connection resets (ECONNRESET), connection refused (ECONNREFUSED), unexpected EOF, and network timeouts - Server errors: HTTP 500, 502, 503, 504 HTTP 429 is intentionally excluded because doWIFRequestWithRetry already handles it with its own backoff strategy for WIF provider operations. Context cancellation and deadline errors are never retried — they represent intentional caller decisions. Retry parameters: 3 retries (4 total attempts), 1s initial backoff doubling each attempt, capped at 10s, with 50-100% jitter. The backoff delay function is injectable via retryDelayFn for test determinism. Closes #6546
|
🤖 Finished Review · ✅ Success · Started 4:10 PM UTC · Completed 4:31 PM UTC Commit: Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $6.89 |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
Risk Assessment: moderate (2/5) DetailsScore unchanged from prior assessment at moderate (2). The BLAST_RADIUS=large flag in Tier 1 is offset by all other metadata signals scoring 1 (no protected paths, no security-sensitive files, strong 67% test ratio, bot author). Tier 2 shows moderate churn on gcp_test.go and an active fix-commit history in this GCP area. Tier 3 confirms the PR scope is proportional to the linked bug and rollback is safe. Previous runRisk Assessment: moderate (2/5) DetailsLow-risk bug fix adding retry-with-backoff to GCP client with strong test coverage (67% test files), but moderate git history churn on the test file and behavioral modification to existing client code nudge the score to moderate. Previous run (2)Risk Assessment: moderate (2/5) DetailsLow-risk bug fix adding retry-with-backoff to GCP client with strong test coverage (67% test files), but moderate git history churn on the test file and behavioral modification to existing client code nudge the score to moderate. |
ReviewFindingsLow
Previous runReviewFindingsMedium
Low
Next steps:
Previous run (2)ReviewFindingsMedium
Low
Next steps:
|
|
🤖 Finished Fix · ✅ Success · Started 4:33 PM UTC · Completed 4:42 PM UTC Commit: Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $1.89 |
DoRequest retried transient transport errors (ECONNRESET, timeout, EOF) for all HTTP methods including non-idempotent ones (POST, PATCH). If a transport error occurred after the server processed the request but before the response arrived, the retry would re-send, risking duplicate side effects (e.g. AddSecretVersion creating an extra secret version). Gate transport-error retries on isIdempotentMethod (GET, HEAD, PUT, DELETE), matching the convention in internal/forge/gitlab and internal/forge/jira. Status-code retries (500/502/503/504) remain unchanged — the server responded explicitly in those cases. Addresses #6861
🔧 Fix agent — iteration 1 (bot-triggered)Restricted transport-error retries in DoRequest to idempotent HTTP methods (GET, HEAD, PUT, DELETE) by adding isIdempotentMethod guard, matching the established convention in internal/forge/gitlab and internal/forge/jira. Added tests covering POST and PATCH not being retried on transport errors. The five low-severity findings were all acknowledged as acceptable by the reviewer and require no changes. Fixed (1):
Disagreed (5):
Tests: passed Decision points
Next steps:
|
|
🤖 Finished Review · ✅ Success · Started 4:43 PM UTC · Completed 5:04 PM UTC Commit: Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $5.96 |
|
🤖 Finished Fix · ✅ Success · Started 5:06 PM UTC · Completed 5:13 PM UTC Commit: Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $1.45 |
Apply the same isIdempotentMethod guard to the 5xx status-code retry path that already protects the transport-error retry path. Without this, non-idempotent methods (POST, PATCH) were retried on 500/502/503/504, risking duplicate side effects (e.g. AddSecretVersion POST returning 500 after creating the version would be retried, creating a duplicate). This aligns with the convention in internal/forge/gitlab and internal/forge/jira, which both gate 5xx retries on method idempotency. - Add isIdempotentMethod(method) check to status-code retry condition - Update DoRequest doc comment to clarify unified idempotency policy - Add TestDoRequest_DoesNotRetryNonIdempotentOnStatusCode (POST/PATCH × 500/502/503/504 = 8 sub-tests) - Change TestDoRequest_RetryPreservesRequestBody from POST to PUT (idempotent) so body-preservation is still tested under retries Addresses #6861
🔧 Fix agent — iteration 2 (bot-triggered)Fixed the medium-severity finding: added isIdempotentMethod guard to the 5xx status-code retry path so non-idempotent methods (POST, PATCH) are no longer retried on server errors. This prevents potential duplicate side effects (e.g. AddSecretVersion). Updated doc comment, added 8 new test cases, and fixed existing body-preservation test. The 5 low-severity findings were all informational observations that the reviewer explicitly acknowledged as acceptable. Fixed (1):
Disagreed (5):
Tests: passed Decision points
Next steps:
|
|
🤖 Finished Review · ✅ Success · Started 5:16 PM UTC · Completed 5:35 PM UTC Commit: Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $5.11 |
Superseded by updated review
|
🤖 Finished Retro · ✅ Success · Started 6:52 PM UTC · Completed 7:07 PM UTC Commit: Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $6.03 |
Retro: PR #6861 — retry-with-backoff for GCP clientThis fully agent-driven PR (code → 3 reviews → 2 fixes → human approval → merge) added retry-with-exponential-backoff to Timeline
What worked well
Evidence for existing issues
AssessmentThe workflow operated correctly — the review agent caught genuine bugs, the fix agent resolved them, and the final code is solid. The main inefficiency was the fix agent not generalizing its idempotency fix across both retry paths in the same function, costing an extra iteration. All improvement opportunities are tracked by existing open issues. No new proposals needed. |
Summary
Add retry-with-exponential-backoff to the GCP HTTP client (
internal/gcp/client.go) for transient transport errors and server error status codes. This fixes WIF pool provisioning failures caused by TCP connection resets duringfullsend inference provision, and makes all GCP API callers (Secret Manager, Cloud Run, IAM bindings) resilient to transient network issues.Related Issue
Closes #6546
Changes
DoRequestwith 3 retries, exponential backoff (1s base, 10s cap), and 50-100% jitterisRetryableTransportErrorfor connection resets (ECONNRESET), connection refused (ECONNREFUSED), unexpected EOF, and network timeouts — following the pattern frominternal/fetch/fetch.goisRetryableStatusCodefor HTTP 500/502/503/504 (429 excluded to avoid double-retry withdoWIFRequestWithRetry)retryDelayFnfield toClientfor test determinism (zero-delay inNewClientWithHTTP)get_after_template_update_failureto account for DoRequest's new retry behavior on 500 responsesTesting
make lintpasses (stage changes first, then run)Closes #6546
Post-script verification
agent/6546-retry-gcp-iam-api)74aebe0ffe9e1a6bb6e1e8a6c7a4b97917457be7..HEAD)