Skip to content

Fix Bun restore ignoring user registry config (#1276) - #1283

Merged
Mikola Lysenko (mikolalysenko) merged 7 commits into
mainfrom
agent/fix-bun-restore-user-registry-config
Oct 9, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 7 commits into
mainfrom
agent/fix-bun-restore-user-registry-config

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #1276

Summary

Hosted Bun unwinds (rollback, remove <purl>, and the hosted → vendored takeover for bun.lock and bun.lockb) now look up each package's registry the way Bun does. That includes the user's .npmrc and the global bunfig, not only the files beside the lock.

Before this, a private scope set only in ~/.npmrc or ~/.bunfig.toml (the usual place, since tokens aren't committed) was looked up on the public registry, without credentials, which also leaked the private package name. The restore then either failed with a 404 or exited 0 after writing "" plus a same-name public package's integrity. That lock breaks cold frozen installs on Bun ≥ 1.3.7 and installs the public package on Bun ≤ 1.3.6.

Root cause

BunRegistrySettings::read (crates/socket-patch-core/src/patch/redirect/upstream/npm.rs) read only {lockdir}/.npmrc and {lockdir}/bunfig.toml. It modelled one project-level config, not Bun's layered one.

Measured Bun behaviour

Measured with a request-logging mock registry on Bun 1.1.39, 1.2.23, 1.3.6, 1.3.7, 1.3.9, 1.3.12, 1.4.0 and 1.4.2 (Linux). Every layer below was varied pairwise.

  • User .npmrc: $XDG_CONFIG_HOME/.npmrc when that file exists, else ~/.npmrc. NPM_CONFIG_USERCONFIG is ignored.
  • Global bunfig: $XDG_CONFIG_HOME/.bunfig.toml when XDG_CONFIG_HOME is set (Bun then never reads ~/.bunfig.toml), else ~/.bunfig.toml.
  • Merging: settings merge per key. A key in a project file beats the same key in the user or global file of the same kind, and unrelated keys still come from the user or global file. A scope entry beats any default registry. The env registry beats the configured default registry but not a scope.
  • Precedence flip in Bun 1.4.0: Bun ≤ 1.3.x takes any .npmrc key over any bunfig key. Bun ≥ 1.4.0 does the reverse.
  • Lock versions: Bun 1.4 is the first to write lockfileVersion: 2, and Bun 1.3 ignores a v2 lock. Bun 1.4 keeps a v1 lock as v1, so a v0/v1 lock (or a bun.lockb) doesn't say which Bun installs it.

The fix

  • BunConfigFiles holds the .npmrc layers (project, then user) and the bunfig layers (project, then global). bun_user_config_paths finds the user files as measured above. bun_lookup_layered resolves each key from the highest-precedence file that sets it, in either kind order.
  • BunConfigOrder:
    • A v2 bun.lock uses bunfig-first (Bun ≥ 1.4).
    • A v0/v1 bun.lock or a bun.lockb is Unknown. A package is refused, with the existing checkout remedy, only when the two orders give a different registry or credentials. Otherwise the restore proceeds as before.
  • Variable expansion: the user's own files expand any ${VAR}, as Bun does (e.g. //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}). Project files keep the existing NPM_TOKEN / NODE_AUTH_TOKEN / BUN_AUTH_TOKEN allowlist. Credentials are never printed: refusals show only bases, which have their userinfo stripped.
  • Both callers (restore_bun_locks, and the bun.lockb takeover restore) go through refuse_ambiguous before fetching.
  • CLI_CONTRACT.md and docs/ecosystems.md describe the layering, the version split and the refusal.

Behaviour change for maintainers: a project whose own .npmrc and bunfig.toml name different registries for the same package, on a v0/v1 bun.lock, used to be restored npmrc-first. It is now refused with the checkout remedy, because Bun 1.4 would resolve it bunfig-first. On a v2 lock it now follows Bun 1.4 (bunfig-first) instead of npmrc-first. The flip is real but small in practice: it only matters when both kinds set the same key differently.

Also included: one rustfmt-only line in upstream/mod.rs. Main isn't cargo fmt --check clean on it, and the cargo fmt --all step required before marking ready reformatted it.

Tests (red → green)

Issue case Test Without fix With fix
#1276 scope + token only in ~/.npmrc registry::bun_remove_reads_registries_set_only_in_the_user_npmrc exit 1, cannot restore … pristine lock
#1276 ~/.bunfig.toml [install.scopes] registry::bun_remove_reads_registries_set_only_in_the_global_bunfig exit 1 pristine lock
$XDG_CONFIG_HOME/.npmrc / .bunfig.toml beat ~ decoys registry::bun_remove_reads_the_xdg_config_home_npmrc_and_bunfig exit 1 pristine lock
v2 lock: global bunfig beats project .npmrc (Bun 1.4) registry::bun_remove_on_a_v2_lock_takes_bunfig_over_npmrc upstream_registry_fallback pristine lock, no fallback
v1 lock, .npmrc vs bunfig disagree registry::bun_remove_refuses_when_npmrc_and_bunfig_disagree_on_a_v1_lock exit 0, guessed refused, pin kept
Real Bun: scope only in $XDG_CONFIG_HOME/.npmrc, scan → rollback → fresh frozen install e2e_redirect_bun_build::bun_redirect_rollback_reads_a_scope_set_only_in_the_user_npmrc HTTP 404 Not Found from the default registry (the issue's symptom) byte-exact lock, original bytes installed

The integration tests are in crates/socket-patch-cli/tests/in_process_vendor_bun_takeover/registry.rs. Core unit tests in npm.rs:

  • bun_user_config_paths_follow_bun_s_lookup
  • bun_layers_take_each_key_from_the_highest_file_that_sets_it
  • bun_user_files_expand_any_variable_project_files_only_token_ones
  • bun_refuses_a_registry_the_lock_s_bun_version_would_decide

The existing #992 tests still pass unchanged.

in_process_vendor_bun_takeover's runner now also removes XDG_CONFIG_HOME, so a developer's user config can't steer the fixtures. HOME was already pinned to a stand-in.

Commands run locally

  • cargo fmt --all -- --check: ok.
  • cargo clippy --locked --workspace --all-features -- -D warnings: ok. --all-targets reports nothing in the changed files; its other findings are in files this PR doesn't touch.
  • cargo test -p socket-patch-cli --all-features --test in_process_vendor_bun_takeover: 40/40.
  • cargo test -p socket-patch-core --lib redirect::upstream: 119/119.
  • SOCKET_PATCH_BUN_E2E_REQUIRED=1 SOCKET_PATCH_BUN_E2E_VERSION=1.4.2 cargo test -p socket-patch-cli --all-features --test e2e_redirect_bun_build -- --include-ignored: 36/36. The 1.1.45 / 1.2.23 / 1.3.14 legs run in CI.
  • cargo test --workspace --all-features --no-fail-fast: 13 failures, all environmental and none in Bun or redirect code:
  • Wrappers under npm/, pypi/ and gem/ only dispatch to the binary, so they need no change.

Follow-ups (not in this PR)

🤖 Generated with Claude Code


Note

Medium Risk
Changes hosted lockfile restoration and private-registry lookup (including credentials), but ambiguity is refused and behavior is heavily tested; v0/v1 locks with conflicting project npmrc/bunfig now fail restore where npmrc-first guessing used to succeed.

Overview
Hosted Bun unwinds (rollback, remove, and hosted → vendored takeover) now resolve each package’s registry the way Bun does, not only from .npmrc / bunfig.toml beside the lock.

The upstream restore loads layered config: project then user .npmrc ($XDG_CONFIG_HOME/.npmrc or ~/.npmrc), project then global bunfig.toml, plus env registry overrides. Keys merge per Bun’s precedence (project beats user/global; Bun ≥ 1.4 prefers bunfig over .npmrc, ≤ 1.3 the opposite). On lockfileVersion 2 restores use bunfig-first; on v0/v1 bun.lock or bun.lockb it refuses pins when the two orders would pick different registries or credentials, instead of guessing.

Credential handling matches Bun: user-owned config may expand any ${VAR}; project files still only expand NPM_TOKEN / NODE_AUTH_TOKEN / BUN_AUTH_TOKEN. Refusal messages expose registry bases only.

Docs (CLI_CONTRACT.md, docs/ecosystems.md) and tests cover user-only scopes, XDG paths, v2 bunfig-over-npmrc, v1 conflicts, and e2e rollback with scope in user .npmrc.

Reviewed by Cursor Bugbot for commit f637b68. Configure here.


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
A hosted Bun rollback, remove or takeover restore looked up each
package's registry only in the .npmrc and bunfig.toml beside the lock.
A private scope set in ~/.npmrc or the global bunfig, where scope
tokens usually live, was looked up on the public registry instead. The
restore then failed, or wrote "" plus a same-name public package's
integrity and exited 0 (#1276).

The restore now reads the files Bun reads, measured on Bun 1.1.39 to
1.4.2: $XDG_CONFIG_HOME/.npmrc when it exists, else ~/.npmrc, and the
global bunfig at $XDG_CONFIG_HOME/.bunfig.toml when XDG_CONFIG_HOME is
set, else ~/.bunfig.toml. A key set in the project's file wins over the
user's file of the same kind. The user's own files expand any variable,
as Bun does; the project's still expand only the npm token variables.

Bun 1.4 also flipped which kind wins a key both set: a bunfig over an
.npmrc, where Bun 1.3 and older did the reverse. A lockfileVersion 2
bun.lock is only ever Bun 1.4's, so it takes the bunfig. On an older
bun.lock (Bun 1.4 keeps it as is) or a bun.lockb, a package the two
kinds disagree on is refused with the checkout remedy, not guessed.

Fixes #1276

Assisted-by: Claude Code:claude-opus-5-5
Adds a real-Bun leg where the scoped package's registry is set only in
the user's $XDG_CONFIG_HOME/.npmrc, the usual home of a private scope.
Hosted scan, then rollback, must restore bun.lock byte for byte, with
the scope registry's tarball URL in the slot. A fresh frozen install
with the same user config must then land the original bytes (#1276).

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 9, 2026 15:12
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/patch/redirect/upstream/npm.rs
A FIFO or device at $XDG_CONFIG_HOME/.bunfig.toml (picked without an
is_file check) or ~/.npmrc blocked the hosted restore. Read both user
files through read_regular_to_string, as the project files already are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUw5kpF3tzHQGxmpTQzo5S
Comment thread crates/socket-patch-core/src/patch/redirect/upstream/npm.rs
A token-only [install.scopes] entry in the user's bunfig took the
configured default registry, even when the project's .npmrc or
bunfig.toml named it. A repository could then pick the host that
received the user's scope credential on rollback, remove or the
bun.lockb takeover. Attach that token only to a default registry
from the user's own files, or npmjs when they set none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUw5kpF3tzHQGxmpTQzo5S
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator Author

Bun patch compatibility / native (*), e2e (ubuntu-latest, e2e_safety_pnpm) and hosted-e2e are red on f637b68. They all look like the public patch API no longer serving npm patches, not this PR.

  • hosted-e2e (e2e_hosted_production, which failed on all 3 attempts): preflight_required_patches_are_published fails. That test only checks that the patches the suite needs are still published. Every npm-ecosystem lane fails too, each with redirect.redirected: 0 and no patches: npm_package_lock, npm_shrinkwrap, pnpm, yarn_classic, yarn_berry, bun, vlt and canary_patches_name_advisories…. The PyPI (uv) and RubyGems (bundler) lanes pass on the same runner.
  • native (*): every Bun version, on Linux and Windows, fails at the first scan. It reports packagesWithPatches: 0 for pkg:npm/minimist@1.2.2.
  • e2e_safety_pnpm: apply against the public proxy leaves index.js unpatched, with its original hash.

This PR only changes how Bun registry config is read on the hosted restore paths (rollback, remove, and the bun.lockb takeover). It doesn't touch the patch search, apply, or the npm, pnpm, yarn or vlt code.

The Bun workflow passed on main at 0ce8d6c (14:58), and on the fix/gc-report-json-1257 branch at 15:03. main's later runs are cancelled or still queued. I can't reach patches-api.socket.dev from my sandbox to check it directly.

No fix exists in the repo for this. I'll re-run the failed jobs once after these runs finish, and compare against main's pending runs.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Update on the red Bun checks: main fails the same way. Bun patch compatibility on 31383f5 (run 37951436371, 15:24–16:01) has all 23 native jobs failing, with only build and binary passing. So the patch API failure described above is not this PR's.

I've re-run the failed jobs once on both of this PR's runs (37952898642 and 37952898726). The rest of CI on f637b68 is green (169/172 jobs), and the PR has no merge conflict.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 f637b68. Configure here.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Picked this PR up because its heartbeat was stale. On f637b68 it's green except for three checks, and none of them come from this PR:

Next: once #1301 or #1302 merges, merge main into this branch and re-check CI.


Generated by Claude Code

@mikolalysenko
Mikola Lysenko (mikolalysenko) added this pull request to the merge queue Oct 9, 2026
@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 9, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Ready for review at 8f948e739921.

  • CI: required checks ci-ok and clippy green; 8 check suites succeeded. 1 superseded workflow run(s) show as cancelled; the required gates passed on this head.
  • Mergeable against main, no CHANGELOG.md change.
  • Bugbot reviewed this head; no unresolved review threads.

Labeled Ready for review by the burn-down agent. Slack announcement pending (connector unavailable this run).


Generated by Claude Code

@mikolalysenko
Mikola Lysenko (mikolalysenko) removed this pull request from the merge queue due to a manual request Oct 9, 2026
@mikolalysenko
Mikola Lysenko (mikolalysenko) added this pull request to the merge queue Oct 9, 2026
Merged via the queue into main with commit 1ecb42c Oct 9, 2026
5 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-bun-restore-user-registry-config branch October 9, 2026 21:40
Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Oct 9, 2026
Resolve conflicts with #1026 (credential redaction) and #1283 (Bun
restore user registry config):

- vendor/{cargo,composer_lock,gem,golang,npm_common,npm_dir,pypi,
  service_fetch}.rs: keep this branch's Result-based service fast paths
  and route the vendor_prebuilt_downloaded advisory through main's single
  VerifiedArchive::downloaded_warning builder.
- patch/redirect/upstream/bun_lockb.rs: keep by_uuid/refuse_all_in from
  upstream/mod.rs (where this branch moved them) and import main's
  BunConfigOrder from upstream/npm.rs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Oct 9, 2026
Resolve conflicts with #1319 (berry implicit node-gyp) and #1283 (Bun
user registry config):

- NpmDist carries both `bin` (#1131) and `node_gyp` (#737).
- #1319 replaced Pin's `bin` with `manifest`; the url-pin restore now
  passes a manifest holding only the version document's bin (and a
  declared node-gyp so render_pinned_entry keeps the entry's
  dependencies), and runs before the implicit node-gyp re-add so that
  re-add is not undone.
- CLI_CONTRACT.md npm-family paragraph merged word by word: keeps the
  berry bin restore note and the Bun user-config registry rules.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

3 participants