Skip to content

Fix Bun patches keyed by a rewired resolution (#1019) - #1699

Merged
Mikola Lysenko (mikolalysenko) merged 5 commits into
mainfrom
agent/fix-bun-patched-dependency-resolution-key
Oct 23, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 5 commits into
mainfrom
agent/fix-bun-patched-dependency-resolution-key

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Fixes #1019

Summary

On Bun 1.4, a bun patch made after socket-patch rewired a package is keyed by the package's current resolution. That is name@https://patch.socket.dev/…/name-1.0.0.tgz when hosted, or name@.socket/vendor/npm/<uuid>/name-1.0.0.tgz when vendored. Bun applies the patch only while the lock still resolves the package there. Before this PR, any socket-patch run that moved the entry orphaned the key, and the user's own patch silently stopped applying on every install, with exit 0 and no warning. Those runs were a superseding hosted re-run, a hosted⇄vendored takeover, a vendored re-run onto a new uuid, rollback/remove, and vendor --revert.

What changes for users:

  • Scan / get / vendor (hosted and vendored): a package whose bun patch is keyed by a hosted URL or vendored path stays where it is, and the run warns redirect_bun_patched_dependency_skipped (hosted) or refuses vendor_lock_entry_unsupported (vendored), naming the key. That covers the superseding re-run, both takeovers (the takeover keeps the existing wiring: redirect_takeover_kept_vendored, or the hosted lock untouched), and vendored re-runs. --allow-patch-conflicts does not lift this, just as it doesn't for the Hosted and vendored Bun rewiring silently discards the project's own bun patch (patchedDependencies): fresh frozen installs drop the user's patch with exit 0 #367 registry key. A key for the URL or path the run would pin anyway (an in-sync re-run at the same uuid) is no conflict, so those runs stay quiet and VEX still attests.
  • Rollback / remove / vendor --revert: these still restore the registry entry the user asked for. A wet run that takes out of the lock the tarball a patch is keyed by now warns redirect_bun_patched_dependency_dropped (hosted unwind) or vendor_bun_patched_dependency_dropped (vendored revert), naming the key and how to re-create the patch. Each unwind compares the keys the lock resolves to before and after it writes, so a stale key, or one the unwind puts back in force, stays silent.
  • Scoped packages: the consent gate from fix(cli)!: require consent before patching user-patched packages #1646 (user_patch_conflict) split a patchedDependencies key at its last @. For a scoped package's resolution key, that @ sits inside the URL or path (…/npm/@s/p/…), so the key named no package and the scoped package went unprotected. It now splits at the first @ past the scope, as Bun does.

Root cause

Both places that recognize a project-owned Bun patch assumed the key is name@<semver or range>:

  1. vendor::bun_lock_text::patched_dependency_key, the Hosted and vendored Bun rewiring silently discards the project's own bun patch (patchedDependencies): fresh frozen installs drop the user's patch with exit 0 #367 backend guard shared by the hosted text and binary rewriters, the in-memory hosted engine, and the vendored text and binary backends. It matched only name@version and a bare name. It now also matches name@<…>/<leaf>-<version>.tgz, the package's own tarball, unless that is the resolution this run writes (dep.artifact_url for hosted, <uuid_dir_rel>/<leaf>-<version>.tgz for vendored). For the unwind warnings, vendor::bun_lock::live_user_patch_keys reads the keys whose name@resolution is a current lock entry spec (text bun.lock, or bun.lockb). The hosted unwind and revert_bun_opts warn for the keys that leave that set.
  2. patch::user_patches::split_selector split at the last @.

Test evidence

Red→green: each new test below fails on main and passes on this branch. To check, I put main's sources back with the new tests kept.

Covers Test Red on main Green here
scoped / unscoped resolution keys protected by the consent gate patch::user_patches::tests::bun_resolution_keys_protect_scoped_and_unscoped_packages ✗ ✓
guard matches hosted + vendored + scoped resolution keys; in-sync target is no conflict; detail wording vendor::bun_lock_text::tests::patched_dependency_key_matches_rewired_resolutions ✗ ✓
hosted text rewriter keeps the old URL on a superseding re-run (scoped too); in-sync re-run is silent patch::redirect::tests::bun_lock_patch_keyed_by_hosted_url_survives_superseding_rerun ✗ ✓
vendored backend refuses before any write (hosted-URL and vendored-path keys, lock on that tarball or on the registry); in-sync vendors; revert warns vendor::bun_lock::tests::user_bun_patch_keyed_by_resolution_refuses_before_any_write ✗ ✓
CLI: superseding hosted re-run, default and --allow-patch-conflicts in_process_vendor_bun_takeover user_patch::bun_patch_keyed_by_hosted_url_survives_a_superseding_rerun ✗ ✓
CLI: hosted → vendored takeover refuses, hosted lock intact user_patch::bun_patch_keyed_by_hosted_url_blocks_the_vendored_takeover ✗ ✓
CLI: vendored → hosted takeover keeps the vendored wiring user_patch::bun_patch_keyed_by_vendored_path_blocks_the_hosted_takeover ✗ ✓
CLI: hosted rollback warns redirect_bun_patched_dependency_dropped user_patch::bun_rollback_warns_that_a_patch_keyed_by_the_hosted_url_stops_applying ✗ ✓
CLI: vendor --revert warns vendor_bun_patched_dependency_dropped user_patch::bun_vendor_revert_warns_that_a_patch_keyed_by_the_vendored_path_stops_applying ✗ ✓

The issue's matrix maps onto these as follows: hosted superseding re-run (text lock) → rewriter + CLI re-run tests; bun.lockb hosted re-run → same guard via the in-memory engine and rewrite_bun_binary callers (skip_bun_user_patched); hosted→vendored and vendored→hosted takeovers → the two CLI takeover tests; vendored superseding re-run → vendored unit test (the guard runs in preflight_package for every entry state); rollback → CLI rollback test; vendor --revert → CLI revert test.

Local runs:

  • cargo fmt --all -- --check: clean
  • cargo clippy --workspace --all-features -- -D warnings: clean
  • cargo test -p socket-patch-core --all-features: 6704 passed, 4 failed. The 4 also fail on main's sources in this sandbox, because they are permission tests that break when run as root: wire_failure_rolls_back_already_written_files, wire_write_failure_maps_error_and_leaves_lock_untouched, relax_loop_must_not_traverse_symlinked_root, an_unremovable_hidden_lock_keeps_every_store_entry.
  • cargo test -p socket-patch-cli --all-features, the suites this touches: --lib 933, in_process_vendor_bun_takeover 45, rollback 122, scan 245, user_patches 27, in_process_rollback_vendored 22, vendor_eject_bun_lockb 10, all passing. in_process_redirect is 148 passed and 3 failed; the 3 write-failure tests also fail on main as root. The whole CLI crate doesn't fit in this sandbox's disk allowance, so CI runs the rest.
  • Real Bun 1.4.4 with SOCKET_PATCH_BUN_E2E_REQUIRED=1 … -- --include-ignored: e2e_redirect_bun_build 38, e2e_vendor_bun_build 40, mode_migration_bun 35, all passing.

Notes

  • Hermetic tests use the key spellings the issue measured with real Bun 1.4.2 / 1.4.3. I didn't add a new real-bun e2e for bun patch --commit after a rewire.
  • patch::redirect::vlt_heal::tests::an_unremovable_hidden_lock_keeps_every_store_entry fails locally on main too, because this sandbox runs as root. It's unrelated.
  • No wrapper (npm/, pypi/, gem/) changes are needed: this is all Rust-side lock logic.
  • /code-review high findings:
    • Fixed: unwind warnings for keys the unwind did not drop; the duplicate warning on a pre-v5 revert followed by a hosted unwind; the duplicated bun_purls filter in hosted_unwind.
    • Not changed: the backend guard stays fail-closed for a stale key. A tarball key the lock no longer resolves to still blocks a later rewire. The hosted→vendored and vendored→hosted takeovers run the guard after reverting the wiring the key names, so at that point the lock can't tell a stale key from a live one. Gating on liveness let both takeovers drop the patch silently (verified with the CLI tests). The refusal names the remedy, which is to remove the entry.
    • Not changed: the consent gate protects every version for a resolution key. UserPatchIndex already treats a selector it can't parse as protecting every version of the name (fix(cli)!: require consent before patching user-patched packages #1646's "unknown ranges protect every version" rule). A resolution key falls under that rule for scoped and unscoped packages alike. This PR only makes scoped keys reach it.
    • Not changed: ?query/#fragment/\\ spellings. socket-patch's own hosted URLs and vendored paths, which are the keys Bun writes after one of our rewires, never carry them, and bun.lock uses /.
    • Not changed: extra reads per revert. live_user_patch_keys returns after reading package.json and the text lock when no key names a tarball. A revert already reads and writes the same lock.
  • CHANGELOG.md is untouched.

https://claude.ai/code/session_014dTNrFC4Epo2YKb7Ydxm7c


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
A bun patch made after socket-patch rewired a package is keyed by the
tarball it resolves to, such as
@s/p@.socket/vendor/npm/<uuid>/@s/p-1.0.0.tgz. The consent gate split
that key at its last @, which sits inside the path for a scoped
package, so the package went unprotected. Split at the first @ past
the scope, as Bun does.

Refs #1019

Assisted-by: Claude Code:claude-opus-5-5
Bun 1.4 keys a bun patch made after a rewire by the package's current
resolution. A superseding hosted re-run, a takeover or a vendored
re-run moved the entry and orphaned that key, so the user's patch
silently stopped applying, even with --allow-patch-conflicts. The bun
guard now matches those keys and leaves the package where the patch
applies (a key for the URL this run would pin anyway is no conflict).
Rollback, remove and vendor --revert still go ahead, but now warn that
such a patch stops applying.

Refs #1019

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) force-pushed the agent/fix-bun-patched-dependency-resolution-key branch from 8e3c884 to cef3ad4 Compare October 23, 2026 20:00
Rollback, remove and vendor --revert warned about any tarball-keyed
bun patch for a package, even a stale key the lock no longer resolved
to, or one the revert put back in force (a pre-v5 vendored revert that
returns the lock to its hosted URL). They now compare the keys the lock
resolves to before and after the write and warn only for the ones it
took out, so a revert followed by a hosted unwind no longer warns twice.

Refs #1019

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 23, 2026 20:54
@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/vendor/bun_lock.rs
The unwind's Bun patch check opened bun.lockb with a bare read, so a
FIFO or device at that path could hang rollback, remove and vendor
--revert. It now reads the lock the way the rest of the Bun backend
does.

Refs #1019

Assisted-by: Claude Code:claude-opus-5-5
@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 979f3db. Configure here.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 23, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review.

  • Head: 979f3db80af6a51b32c02c478e84165748dfd7b0
  • CI: all check runs on the head settled green (35 success, 19 path-skipped, 0 failing); no main-wide failures.
  • Bugbot: reviewed 979f3db with no new issues; the one earlier thread (bun_lock.rs:936) is resolved.
  • Mergeable against main, no CHANGELOG.md change.
  • Reviewer focus: the patched_dependency_key matcher for name@<url-or-path>/<leaf>-<version>.tgz keys and the scoped-package split in user_patches::split_selector.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review.

  • Head: 979f3db80af6a51b32c02c478e84165748dfd7b0
  • CI: 54/54 check runs settled green on this head (35 success, 19 path-skipped, none failing; no main-wide failures).
  • Bugbot: reviewed 979f3db with no new issues; the one earlier thread (bun_lock.rs) is resolved.
  • Reviewer focus: the name@<resolution> key split in the consent gate and the matching bun backend guard (patch/user_patches.rs, vendor/bun_lock*.rs).

Generated by Claude Code

Merged via the queue into main with commit 99551b2 Oct 23, 2026
54 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-bun-patched-dependency-resolution-key branch October 23, 2026 22:55
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