Skip to content

Registry downloads give up after 60 s even while the body is still arriving #872

Description

[agent] Filed by the scheduled architecture audit routine (CLI and core). Register: register comment.

Kind: bug. Source: new finding, register row C49. It relates to C15: tracking issue #676 leaves RegistryClient out of scope.

Problem (main @ 0d302dc)

socket-patch has one stated HTTP transport policy, api::retry::ApiTimeouts: a 10 s connect bound, plus 60 s of silence that resets on every chunk, with no total deadline. #581 chose this so that large bodies can stream for as long as they keep making progress.

The registry clients don't use it. They set a whole-request deadline instead:

Next to this, registry_fetch::download is the last hand-rolled copy of utils::http::read_capped: the same declared-length check and streamed cap, with different messages. Maven, self-update, the vendor service and vlt_preflight already use the shared reader.

Proof by execution. This was a temporary core integration test, run twice on 0d302dc and not committed. A local server answers 200 with Content-Length and then sends a 64 KiB chunk every second for 70 s, which is a slow but steady download. Both clients read the same URL at the same time:

AUDIT registry_client: Err("chunk after 3932160 bytes: timeout=true error decoding response body") after 60.000973582s
AUDIT api_timeouts:    Ok(4587520) after 69.094452842s

The second run gave identical results (60.0015 s and 69.099 s). The registry client aborted a transfer that was still progressing; the ApiTimeouts client finished it.

Symptoms

None filed. Impact:

  • On a slow or shared link, rollback (or a takeover) of a hosted pin whose original artifact takes more than 60 s to download fails with a transport error.
  • The same happens to vendoring a Maven artifact whose original jar isn't in the local repository.
  • Retrying doesn't help, because the same bytes need the same time.
  • A black-holed registry host also holds a run for the full 60 s, because no connect bound is set.

Proposed change

  • build_registry_client builds through ApiTimeouts::default().apply(...) (connect + idle read) instead of .timeout(60 s).
  • maven_repo::fetch_registry_bytes reuses that client, built once per process, instead of building one per fetch.
  • registry_fetch::download keeps its http(s) scheme check and then calls utils::http::read_capped(resp, MAX_DOWNLOAD_BYTES, "registry artifact").
  • Deleted: the hand-rolled capped loop in registry_fetch::download, and the second Client::builder() in maven_repo.rs.

Size and scope

About −35/+10 production lines in vendor/registry_fetch.rs and vendor/maven_repo.rs, plus tests.

Out of scope:

  • retry for registry fetches;
  • caps (per maintainer steering, no new caps on upstream data);
  • the self-update and telemetry clients, which have deliberate total budgets (300 s download, and 5 s per event under C22).

Acceptance criteria

  • grep -n "from_secs(60)" crates/socket-patch-core/src/vendor/registry_fetch.rs crates/socket-patch-core/src/vendor/maven_repo.rs finds no client timeout.
  • grep -c "Client::builder" crates/socket-patch-core/src/vendor/maven_repo.rs prints 0 outside tests.
  • Regression test: a server that trickles a body for longer than the old 60 s budget, with silences under the idle bound, downloads in full through registry_fetch::download. Shorten the bounds with a test-only override rather than waiting 60 s.
  • Regression test: a server that goes silent mid-body past the idle bound still fails with a timeout.
  • The existing download_refuses_lying_content_length and download_caps_streamed_bytes_without_content_length tests stay green. Message assertions may change to the read_capped wording.
  • The upstream restore and vendored Maven suites stay green.

Dependencies

None. It doesn't conflict with #676/#677, which touch only api/client.rs. E29 (moving registry_fetch.rs out of vendor/) would carry this along.

Activity

  1. mikolalysenko commented on Oct 5, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged as priority:p1: the 60 s whole-request deadline in registry_fetch::build_registry_client is used by hosted upstream restore of npm tarballs (npm-family), plus Go zips, NuGet and vendored Maven, so the highest tier applies. Confirmed on main 0d302dc: registry_fetch.rs:48 and maven_repo.rs:1405 both set .timeout(Duration::from_secs(60)) with no connect bound. Not a duplicate; no open PR touches registry_fetch. Standalone root cause (no cluster with other open issues; #676 explicitly excludes RegistryClient).


    Generated by Claude Code

  2. mikolalysenko commented on Oct 5, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming this issue for the architecture refactor routine (highest leverage: p1 bug fixed by moving the registry clients onto ApiTimeouts and deleting the last hand-rolled read_capped copy and Maven's per-fetch client). Branch: arch-refactor/872-registry-timeouts. Claim-ID: 2026-10-05T17:56:09Z-58ef60


    Generated by Claude Code

  3. mikolalysenko commented on Oct 5, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #876.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:triagedarch-auditFiled by a scheduled architecture audit routine (see the architecture review discussion)bugSomething isn't workingpriority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions