feat: implement and refactor network request retries - #378
Conversation
|
💬 Discussion in Slack: #pr-review-cli-378-feat-implement-and-refactor-network-request-retries Posted by Review Police — reviews, comments, new commits, and CI failures will stream into this channel. |
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 1 · PR risk: 0/10 |
|
| Filename | Overview |
|---|---|
| packages/util/retry.go | Defines centralized retry policies, transient-failure classification, method-safety controls, bounded server-directed delays, and retry logging. |
| packages/util/retry_test.go | Covers retry status selection, transport failures, cancellation, TLS errors, method safety, environment overrides, and centralized client construction. |
| packages/util/common.go | Routes shared Resty client construction through the selected retry policy while retaining custom-header parsing. |
| packages/agentproxy/leases.go | Migrates dynamic lease creation and revocation to the shared retry-enabled client. |
| packages/cmd/agent.go | Replaces the agent token-refresh retry override with the centralized long-running agent policy. |
| packages/agentproxy/proxy.go | Applies the bounded best-effort retry policy to proxy usage reporting. |
Reviews (2): Last reviewed commit: "improvement: address feedback" | Re-trigger Greptile
|
@greptile review |
Thiago-AS
left a comment
There was a problem hiding this comment.
Maybe we should tone down the comments lol. There are a lot of them, and they can get stale pretty easily. Most of them are just stating the obvious.
Claude, go over the comments that were added and leave only what’s necessary. Remove anything obvious or self-documented by the code. Keep the comments concise. No em dashes XD
Description 📣
Nothing in the CLI retried
429or504. Resty's built-in default only retries transport errors, and it is silently replaced the moment a retry condition is added, so status codes were never inspected anywhere.State before this PR:
util.GetRestyClientWithCustomHeaders(), 48 call sitesresty.New()sites (agentproxy/*,pam/*,cmd/agent_proxy*.go)INFISICAL_CUSTOM_HEADERSutil/helper.go,cmd/agent.goSetRetryCount(10000)with no conditioncmd/login.goSetRetryCount(5), sameThe
10000is the tell: a connection refused retried ten thousand times while a429failed on the first attempt.Fix: one policy in
packages/util/retry.go, applied insideGetRestyClientWithCustomHeaders(). That single edit covers the 48 existing sites and every futureapi.Call*. The 9 direct constructions were converted, so there is now exactly one construction path, and all 5 ad-hoc overrides are gone.Policy
429,502,503,504, plus typed transport errors (net.Error,ECONNRESET/ECONNREFUSED/EPIPE/EHOSTUNREACH/ENETUNREACH/ETIMEDOUT,io.EOF). Type and errno checks, not substring matching on error text.Retry-Afterin both RFC 9110 forms, capped atMaxDelay. Nothing read that header before.429. A502/503/504can mean the server did process the write and only the response was lost, so replaying a POST risks double-applying it, for instance minting a second dynamic secret lease.429is safe because the server states it rejected the request outright.4xx, bare500, TLS trust failures, orcontext.Canceled/DeadlineExceeded. Allow-list, so anything unrecognised surfaces immediately.INFISICAL_RETRY_*:DefaultRetryPolicy(3 retries / 10s),AgentRetryPolicy(30 / 30s, for the agent's token lifecycle),BestEffortRetryPolicy(1 / 1s, for agent-proxy usage reporting, which also runs on the shutdown path).TestNoDirectRestyConstructionwalkspackages/and fails on any newresty.New(). Without it the choke point silently decays: a direct construction compiles, runs, and looks fine in review, it just has no retries.Type ✨
Behavior changes worth a look
AgentRetryPolicy).cmd/login.go: 5 retries becomes 3, but now covers429/504where it previously covered neither.INFISICAL_RETRY_*warns and falls back to the default instead ofos.Exit(1), since this now runs while building a client for any command. The agent's stricter SDK-side validation is unchanged.INFISICAL_CUSTOM_HEADERS. Latent fix, but a behavior change.Known gap
17 sites build
infisicalSdk.NewInfisicalClient(ssh,dynamic-secrets,gateway,pam, agent templating). The SDK's own retry condition never inspects status codes either, so429/504there still fail on the first attempt. Closing that needs ago-sdkchange, since its opt-inRetryRequestsConfigretries anyIsError()including401and404. Out of scope here. The high-traffic paths are covered:infisical run,secrets get, andexportall go throughpackages/util/secrets.go, which uses the shared constructor.Also still outside resty entirely:
packages/pam/session/chunk_uploader.go:458useshttp.DefaultClient.Tests 🛠️
Unit tests in
packages/util/retry_test.gocover the status-code matrix, POST-vs-GET method safety,Retry-Afterparsing and capping, transport-error classification (including that TLS trust failures are not retried), env overrides, and the choke-point guard.go build ./... go test ./packages/... -count=1 -vet=off go vet ./packages/util/ ./packages/agentproxy/ ./packages/pam/...The guard test was verified to actually fail on a planted
resty.New(), not just pass vacuously.Manual verification
Drove the built binary against a mock API, counting requests server-side rather than trusting the CLI's own logs.
Results:
Retry-After: 2Not run: the
e2e/suite. It builds and vets clean (cd e2e && go build ./... && go vet ./...), but running it needsINFISICAL_BACKEND_DIRand ane2e/.env. Nothing was tested against a real Infisical instance.🤖 Generated with Claude Code
This PR adds/unifies request retry attempts
Type ✨
Tests 🛠️
# Here's some code block to paste some code snippets