Repository navigation
Fix unbounded registry lookups in upstream restore (#1220) - #1444
Merged
Mikola Lysenko (mikolalysenko) merged 3 commits intoOct 12, 2026
Merged
Mikola Lysenko (mikolalysenko) merged 3 commits into
Mikola Lysenko (mikolalysenko) merged 3 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
4 tasks
A rollback, remove or vendored takeover of many hosted pins sent every public-registry lookup at once (40 pins, 40 requests in flight), with no 429 handling and no regard for a tight file descriptor limit. registry_concurrency() existed for exactly this but had no caller. UpstreamClient now holds a semaphore sized by registry_concurrency() (4, or 1 under a tight RLIMIT_NOFILE), and every request it sends, JSON or download, holds a slot from send to the end of its body. The restorers' join_all folds are unchanged, so output order and contents stay the same. Fixes #1220 Assisted-by: Claude Code:claude-opus-5-5
The npm restore test now asks only that lookups overlap, with a 200ms response delay, so a slow CI runner that doesn't line up all four slots at once can't fail it. The cap check itself (never more than registry_concurrency() in flight) is unchanged. Also gives the test server a requests() accessor and says in the client docs that every request it sends takes a slot, private registries and the patch service's checksum metadata included. Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 12, 2026 19:47
Collaborator
Author
|
BugBot review 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 0ba9f7e. Configure here.
Tanmay Singla (Tanmay182003)
approved these changes
Oct 12, 2026
Mikola Lysenko (mikolalysenko)
deleted the
agent/fix-upstream-registry-concurrency
branch
October 12, 2026 20:59
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LLM Description written by Claude Code:claude-opus-5-5
Fixes #1220
Summary
A
rollbackorremoveof hosted pins, and the hosted → vendored takeover and eject, re-resolve each pin's upstream entry from its registry. With many pins, every lookup went out at once: 40 pins meant 40 requests in flight to registry.npmjs.org, PyPI, crates.io, the Go proxy or Packagist. The registry client has no 429 handling, so a rate-limited lookup got its pin refused. Under a tightRLIMIT_NOFILEthe burst could also fail withEMFILE. Now at mostregistry_concurrency()lookups are in flight: 4, or 1 under a tight descriptor limit.Root cause
The five restorers (npm/vlt, PyPI, cargo, Go, Composer) start every pin's lookup together with
join_all.UpstreamClientsent each request on a plain registry client with no cap, andutils::concurrent::registry_concurrency()had no caller.Fix
The cap lives in one place.
UpstreamClientholds atokio::sync::Semaphoresized byregistry_concurrency(), and every request it sends takes a slot from send to the end of its body. That covers JSON GETs and downloads (crates index, Go zip/mod, nupkg, berry archive checks). The issue offered this or fiveordered_concurrentcall sites; I took the client-level one so nested lookups and any future fan-out are covered too. Thejoin_allfolds don't change, so restore output stays byte-identical.Permits never nest. Only
get_json_authorizedanddownloadtake one, and neither calls another client method while holding it, so a cap of 1 can't deadlock.The cap also covers the project's private registry or mirror and the patch service's upstream checksum metadata (
npm_berry_checksum), because both go through this client. The field doc says so.Tests (red → green)
patch::redirect::upstream::npm::tests::restore_lookups_never_exceed_the_registry_cap(a local server counts peak in-flight requests across keep-alive connections)peak 40 > cap 4patch::redirect::upstream::client::tests::a_cap_of_one_sends_lookups_one_at_a_timeleft: 10, right: 1The npm test drives
fetch_dists_on, which every npm-family restorer uses. The other formats go through the same client methods, so the client-level test covers them. That's the option the issue's acceptance criteria allow.Local checks
cargo fmt --all -- --check: clean.cargo clippy --workspace --all-features -- -D warnings: clean.cargo test -p socket-patch-core --all-features --no-fail-fast: 6391 passed in the lib, and every integration test passed. The 4 lib failures are the root-only chmod tests (copy_tree::relax_loop_must_not_traverse_symlinked_root,vlt_heal::an_unremovable_hidden_lock_keeps_every_store_entry,pypi_poetry::wire_write_failure…,pypi_requirements::wire_failure_rolls_back…). They also fail onmainin this sandbox, which runs as uid 0, and other agent PRs list the same ones.in_process_vendor_{bun,npm_v1,pnpm,pypi}_takeoverandmode_migration_{npm,pypi,cargo,vlt,bun}all pass.in_process_redirect: 140 passed, and 3 failed. All three are chmod write-failure tests (partial_lockfile_write_failure…,redirect_json_mode_write_failures…,vlt::scan_redirect_vlt_heal_invalidation_failure_warns), the same root-only class. CI runs as non-root.npm/,pypi/,gem/): they only dispatch to the binary.CI: green on
0ba9f7e(ci-oksuccess). Bugbot: no findings on0ba9f7e./code-review high: findings not fixed here
get_json_authorizedreads JSON bodies with no size cap, and its errors carry the raw URL (userinfo included) wheredownloadredacts. Both predate this PR and are separate from the concurrency cap. Worth a follow-up issue.requests()accessor, and the field doc's scope.Per-issue checklist
registry_concurrency():restore_lookups_never_exceed_the_registry_capa_cap_of_one_sends_lookups_one_at_a_timeupstreamunit tests and the takeover/mode-migration suites passregistry_concurrencyhas a production caller, and clippy shows no dead-code allowance🤖 Generated with Claude Code
https://claude.ai/code/session_01DFfqPEjeRuqL9sPPtsLH2n