Repository navigation
One serve GET and one vendor retry loop in api/client.rs (#1869) - #1870
Mikola Lysenko (mikolalysenko) wants to merge 5 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
api/client.rs fetched a grant-tokenized serve URL through two
near-copies (the vendor archive GET and PatchApi::download_artifact)
and hand-wrote three vendor retry loops around them and the
package-reference POST.
serve_get_once(url, max_bytes, noun) is now the one GET, and
retry_vendor is the one loop; it also takes the deferred first attempt
of a split pypi download, so resuming still spends one budget.
download_vendor_archive_retrying, download_vendor_archive_from,
vendor_download_retry, download_artifact_capped(_once), CappedAttempt
and ServeDownload::Failed are gone.
Errors now read the same on both paths: a size-cap breach is
ApiError::Other("<noun> exceeds the N-byte limit") (the archive path
returned ApiError::Network with read_capped_typed's text), and an
unexpected status goes through status_error, so the artifact path
gains the body detail and an empty body no longer leaves a trailing
": ". PatchApi downloads now log their GET and failed attempts under
--debug. Retry policy values are unchanged.
Assisted-by: Claude Code:claude-opus-5-5
The PatchApi artifact GET never read an error body; through the shared serve GET it would have buffered one unbounded. Read it under the same max_bytes as the success body, so an oversized error page is dropped and only the status is reported. Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
|
Burn-down agent: labeled Ready for review at
Generated by Claude Code |
Final review brief:
|
The error body is only shown in the message, so read it under a 64 KiB cap (or the caller's, if smaller) and the attempt's body timeout; a longer or stalled body is dropped and the status alone is reported. Assisted-by: Claude Code:claude-opus-5-5
|
[agent] Final-review point 3 is addressed in 7fdad36: the error body is now read under Generated by Claude Code |
|
BugBot review Generated by Claude Code |
|
Final reviewer: disarmed auto-merge. The approver brief covered Generated by Claude Code |
7fdad36 capped a serve GET's error body at 64 KiB and bounded its read by the body timeout, but the existing test only used caller caps under 64 KiB and never stalled, so it passed with or without the commit. Add a test for a >64 KiB body under a 1 MiB caller cap and one for a body that stalls after its headers; both fail without 7fdad36. Also update body_timeout's doc, which still said it bounds only the archive read. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011f9aeS41XjBLw8LQsUUTYU
|
bugbot run Final reviewer: I pushed The push also updates Generated by Claude Code |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 95ceea0. Configure here.
|
Ready for review at
Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1869
Summary
This is register row A27 (living document, Part 3: duplication).
api/client.rsfetched a grant-tokenized serve URL through two near-copies:download_vendor_archive_onceserves the vendor archive and the crate-internaldownload_artifact.download_artifact_capped_onceservesPatchApi::download_artifact, which the hosted and in-memory engine use.On top of those, the file hand-wrote three vendor retry loops: one around each GET and one around the package-reference POST. The two copies had drifted on the cap-breach error variant, the status text and the transport text. This PR leaves one GET and one loop.
Changes
serve_get_once(url, max_bytes, noun)replaces both GETs. It returnsResult<ServeDownload, VendorAttemptError>, andServeDownloadloses itsFailedvariant;Okis renamedBytes.retry_vendor(key, what, deferred, once)replaces the three loops. It also takes the deferred first attempt of a split pypi download, sodownload_artifact_resumingstill spends one budget across the split.download_vendor_archive_retrying,download_vendor_archive_from,vendor_download_retry;download_artifact_capped,download_artifact_capped_once, theCappedAttemptalias, and the secondimpl ApiClientblock.download_vendor_archive(url, deferred)remains as the archive entry point, because three callers use it: step 2,download_artifactanddownload_artifact_resuming.Retry-After-over-cap disagreement (ApiRetryPolicygives up,VendorRetryPolicyclamps to 4 s). It is a behavior change, so it belongs in a follow-up.Tests
cap_breach_is_not_retriednow asserts the exact unified error,Other("vendor package exceeds the 268435456-byte limit"), where it used to accept anyNetworkerror containing "too large".every_vendor_retry_loop_…is renamedevery_vendor_retry_path_…. It still covers the reference POST, the archive GET (throughdownload_artifact), thePatchApiGET and a resumed deferral.trait_download_error_body_is_capped. A 500's JSON message shows when the body fits the caller's cap, and an over-cap body is dropped. It is red when the error body is read with uncappedresp.text().trait_download_error_body_is_capped_at_64_kib(a >64 KiB error body under a 1 MiB caller cap shows only the status) andtrait_download_stalled_error_body_is_bounded_in_time(a 503 whose body stalls after the headers returns at the body timeout). Both are red without 7fdad36.deferredinretry_vendor, and bothresuming_a_deferral_spends_one_budget_not_twoandevery_vendor_retry_path_honors_an_http_date_retry_afterfailed.Diff
api/client.rs+155 / −244: production +119 / −238 (net −119), tests +36 / −6.Behavior change
The error text and variant are now unified, as the issue's step 1 asked:
download_artifactPatchApi::download_artifactNetwork("vendor package too large: declared N bytes > M cap"), nowOther("vendor package exceeds the M-byte limit")status_error: the JSONmessageis shown when present, and an empty body no longer leaves a trailing": "artifact download failed with status N, now also carries the body's message--debugGET artifact <url>andartifact download attempt N failed: …Nothing outside
client.rsmatches onApiError::Networkfrom these paths. The retry decision is unchanged because a cap breach was never retried on either path. No test outside this file pins the old text.Verification
cargo clippy --workspace --all-features -- -D warningsis clean.cargo test -p socket-patch-core --lib api::: 264 passed.--test api_retry_e2e --test api_timeout_e2e: 24 passed.cargo test -p socket-patch-core --lib: 6994 passed and 4 failed. All 4 depend on permissions and fail because the sandbox runs as root; none touchesapi/:copy_tree::relax_loop_must_not_traverse_symlinked_rootvlt_heal::an_unremovable_hidden_lock_keeps_every_store_entrypypi_poetry::wire_write_failure_maps_error_and_leaves_lock_untouchedpypi_requirements::wire_failure_rolls_back_already_written_files/code-review high
PatchApipath never did. It is now read under min(caller's cap, 64 KiB) and the body timeout.PatchApipath and the cap-breachNetwork→Otherchange are the unification api/client.rs downloads a served artifact through two near-copy GETs and hand-writes four retry loops under two retry policies #1869 asks for.attempts == 1makes a second request": onlydownload_artifact_first_attemptbuilds aDeferredAttempt, and only whenattempts > 1.download_artifact": that runs the samedownload_vendor_archivecall that step 2 makes.Risk
Low to medium. The change is confined to one file and contains no platform-specific code. The only observable differences are the error texts above.
🤖 Generated with Claude Code
https://claude.ai/code/session_011f9aeQpioTouRYwdeoj9jh
Note
Medium Risk
Changes HTTP client retry, timeout, and error mapping for vendor and artifact downloads; behavior is mostly equivalent but error types/text and error-body handling differ observably.
Overview
Consolidates duplicated grant-tokenized serve downloads and vendor retry logic in
api/client.rsintoserve_get_once(one GET with configurable byte cap and error labels) andretry_vendor(shared loop for package-reference POST, archive GET, andPatchApi::download_artifact, including deferred split-download budgets).ServeDownloadno longer carries failures inline (Failedremoved; success isBytes). Callers getResult<…, (ApiError, bool)>instead. Non-OK responses now read error bodies under min(caller cap, 64 KiB) andbody_timeout, so huge or stalled bodies cannot hang or blow up messages; successful reads still use the caller's cap.Observable error tweaks: size-cap breaches use
ApiError::Otherwith a unified"… exceeds the N-byte limit"string (wasNetworkon the archive path);PatchApi::download_artifactcan include JSONmessagefrom error bodies where the old path only reported status.Reviewed by Cursor Bugbot for commit 95ceea0. Configure here.
Generated by Claude Code