Repository navigation
Tracking: one retry primitive for the patch API client (JSON, vendor service, blob and diff) #676
Description
[agent] Filed by the scheduled architecture audit routine (CLI and core). Register: register comment.
Kind: tracking. Source: review Part 7.2 ("Three retry systems in one module") and R12; register row C15.
Problem
ApiClient has two independent retry policies. A third path, the blob and diff downloads, has none.
| Path | Policy | Retryable | Retry-After |
Jitter |
|---|---|---|---|---|
JSON calls (send_json_request) |
api::retry: 3 retries, a run-wide 60 s window, SOCKET_API_MAX_RETRIES |
429, 503 (retry.rs#L182-L187); #610 adds connect resets |
delta-seconds or HTTP-date (retry.rs#L193-L204) |
seeded SplitMix (retry.rs#L209) |
Vendor service POST/GET and download_artifact (VendorRetryPolicy, vendor_backoff, download_artifact_capped) |
3 attempts, 400 ms → 4 s, no env knob | 429, 500, 502, 503, 504 + transport (client.rs#L327-L329) |
delta-seconds only (retry_after_secs) |
a second jitter_sample() built on RandomState (client.rs#L319-L324) |
Blob and diff (fetch_binary, client.rs#L1066-L1124) |
none | — | — | — |
The same Socket hosts are therefore treated differently depending on the call. Shown by execution on 045d7ec (a temporary unit test, run twice, not committed), with Retry-After: Fri, 27 Mar 2026 19:12:42 GMT:
api::retry::parse_retry_afterwaits until that date;- the vendor path's
retry_after_secsreturnsNoneand falls back to its own backoff; - 500, 502 and 504 are retried on vendor calls but are final on JSON calls.
Target design
One retry primitive in api::retry:
- one
Retry-Afterparser and one jitter source; - a
Classifierper call family, since the retryable statuses legitimately differ (vendor archives build asynchronously, the patch API throttles); - one
retry(op, policy, classifier)loop.
VendorRetryPolicy becomes a preset of that policy, not a second implementation, and fetch_binary gets the JSON preset. Timeouts stay where #581 put them (ApiTimeouts).
Checklist (each item one PR, in order)
- Vendor-service retries ignore an HTTP-date Retry-After: fold the vendor Retry-After parser and jitter onto api::retry #677 (child 1): fold the vendor path's
Retry-Afterparser and jitter ontoapi::retry, deletingretry_after_secsandjitter_sample()fromclient.rs. - Child 2: one
retry_withloop. Replace the three hand-written vendor loops (request_vendor_package,download_vendor_archive_retrying/vendor_download_retryanddownload_artifact_capped) andsend_json_request's loop, keeping each call family's classifier. Not filed yet; it needs child 1 and Retry patch API connections reset mid-handshake #610. - Child 3: retry blob and diff downloads (
fetch_binary) under the JSON preset. Not filed yet; it is blocked by Stream patch blob and diff downloads to disk (#571) #607 (C37), which rewritesfetch_binaryto stream. - Child 4: merge the two near-identical downloaders,
download_vendor_archive_onceanddownload_artifact_capped_once. Not filed yet.
Out of scope: the non-patch-API stacks (RegistryClient, self-update and telemetry clients). Telemetry's per-event client is C22.
Dependencies
- Blocked in part by Stream patch blob and diff downloads to disk (#571) #607 (child 3) and Retry patch API connections reset mid-handshake #610 (child 2: transport retry for JSON calls).
- Related: The public-proxy per-package fallback runs 10 requests in flight, ignoring SOCKET_API_CONCURRENCY and the proxy cap of 4 #614 (C38, pacing) and Only scan, get <uuid> and vex fall back to the public proxy on 401/403; get search, apply, rollback, repair and vendor eject fail #647 (C39, proxy fallback in
ApiClient).
Activity
mikolalysenko commentedon Oct 3, 2026
[agent] Triage: priority:p3 (cross-cutting refactor, tracking). #677 is its first child: the vendor-service
Retry-Afterand jitter copies inapi/client.rs. Those changes land at the same boundary,api::retry. I found no duplicate and no open PR.Generated by Claude Code