[agent] Found by the scheduled Bun bug-hunt routine (ledger #306).
Summary
Vendored mode records bun.lockb wiring (kind: bun_lockb_package, file: bun.lockb) in .socket/vendor/state.json. Bun's documented binary → text migration, bun install --save-text-lockfile, deletes bun.lockb and writes bun.lock. The vendored tuples carry over ("pk-a": ["pk-a@.socket/vendor/npm/<uuid>/pk-a-1.0.0.tgz", {}, "sha512-…"]), but socket-patch still treats the entry as a bun.lockb entry. Every unwind path then breaks:
- Right after the migration:
vendor --revert and rollback exit 1 with revert_failed: "bun.lockb is missing; cannot safely revert the binary lock". scan --mode hosted (the takeover) exits 0 with redirected: 0, redirect_vendored_revert_failed, and the remedy "run socket-patch vendor --revert to clean up". That remedy is the command that just failed. repair and a vendored re-run report success and change nothing. The only way out is git checkout bun.lockb, which undoes the migration.
- After a superseding patch (new uuid) is vendored on the migrated lock: the re-vendor rewrites
bun.lock (exit 0). Because the previous wiring was a bun.lockb record, carry_forward_wiring can't match it, so the new bun.lock record has original: null and the registry original is lost. vendor --revert then exits 0 with status: success, vendor_lock_entry_drifted ("has no recorded pre-vendor original; left as-is (run bun install to re-resolve it from the registry)"), vendor_artifact_kept and vendor_revert_kept. The lock still pins the vendored tarball, and the advised bun install keeps it, because the lock entry satisfies package.json. The project stays patched with no supported way back.
Hosted mode is fine: a hosted bun.lockb migrated to bun.lock rolls back correctly, because hosted rollback re-derives the record from the registry.
Impact
- A team that vendored on
bun.lockb and then followed Bun's own advice to move to the text lock (Bun ≥ 1.2) can no longer revert, roll back or switch to hosted.
- Case 1 fails loudly (exit 1), except for the hosted takeover, which exits 0 having done nothing.
- Case 2 is silent:
vendor --revert reports success (exit 0) while the patched artifact stays wired into the lock and installed. The pre-vendor registry tuple is permanently gone from the state.
Repro
Uses a local mock of the patch API and npm registry: the same routes as e2e_redirect_bun_build.rs (patches/batch, by-package, view, package grant, hosted tarball), with the registry on a different origin from SOCKET_PATCH_SERVER_URL. Any vendored-capable patch reproduces it.
# bunfig.toml points [install] registry at the mock registry
echo '{"name":"app","version":"0.0.0","dependencies":{"pk-a":"1.0.0","pk-d":"1.0.0"}}' > package.json
bun-1.1.45 install # writes bun.lockb (or Bun ≥1.2 with saveTextLockfile = false)
git add -A && git commit -qm init
socket-patch scan --mode vendored --json --yes # exit 0, bun.lockb wired
rm -rf node_modules && bun-1.4.2 install --save-text-lockfile # bun.lockb deleted, bun.lock written with .socket/vendor tuples
# Case 1
socket-patch vendor --revert --json # exit 1, revert_failed: "bun.lockb is missing; cannot safely revert the binary lock"
socket-patch rollback --json --yes # exit 1, same
socket-patch scan --mode hosted --json --yes # exit 0, redirected 0, redirect_vendored_revert_failed → "run `socket-patch vendor --revert`"
# Case 2 (instead of the above): the API now serves a superseding patch (new uuid)
socket-patch scan --mode vendored --json --yes # exit 0, bun.lock re-pinned to the new uuid; state.json wiring: file bun.lock, original: null
socket-patch vendor --revert --json # exit 0 "success": vendor_lock_entry_drifted + vendor_artifact_kept + vendor_revert_kept
rm -rf node_modules && bun install # node_modules/pk-a is still the PATCHED bytes; bun.lock still pins .socket/vendor/…
Expected vs actual
- Expected:
docs/testing/bun-compatibility.md (Binary bun.lockb row) promises "vendor --revert returns the pre-hosted lock", and the vendored section says rollback "restores the original manifest / lock bytes, removes the .socket/vendor state". The vendor state holds everything needed to restore the registry tuple: name, version, the registry resolution and the integrity in the bun_lockb_package original. The text-lock path already restores exactly this shape on a native text lock. If socket-patch must refuse, it should name a remedy that works, and the hosted takeover shouldn't exit 0 with a circular hint. A re-vendor should never discard a recorded original just because the lock changed format.
- Actual: see above. Revert/rollback exit 1 (case 1), or exit 0 with nothing reverted and the pre-vendor original lost (case 2).
Matrix (Linux, main 045d7ec)
bun.lockb writer |
migrated by --save-text-lockfile |
Case 1 (revert / rollback / hosted takeover) |
Case 2 (re-vendor → revert) |
| 1.1.45 |
1.2.23 (v1) |
fail (1 / 1 / 0, nothing done) |
untested |
| 1.1.45 |
1.4.2 (v2) |
fail |
fail (still patched after bun install) |
1.2.23 (saveTextLockfile = false) |
1.2.23 / 1.4.2 |
fail |
untested |
| 1.2.23 |
1.3.14 (v1) |
untested |
fail |
1.4.2 (saveTextLockfile = false) |
1.2.23 / 1.4.2 |
fail |
fail (1.4.2) |
control: text bun.lock from the start, 1.4.2 |
— |
n/a |
pass (revert byte-exact) |
control: bun.lockb not migrated, writers 1.1.45 / 1.4.2 |
— |
n/a |
pass (registry bytes after bun install) |
macOS/Windows: untested (the code paths are OS-independent).
First bad version: not a regression. Release 4.0.0 refused --mode vendored on bun.lockb (exit 1), so the migration never arose there.
Suspect code
crates/socket-patch-core/src/vendor/state.rs:453-473, carry_forward_wiring: an original is carried forward only from a previous record with a matching surface (wiring_surface_matches), so a bun_lockb_package / bun.lockb original never reaches the new bun_lock_package / bun.lock record.
crates/socket-patch-core/src/vendor/bun_lock.rs:536: rewriting "our own" vendored tuple records original: None by design, relying on that carry-forward.
crates/socket-patch-core/src/vendor/bun_binary.rs:419-426, revert: a missing bun.lockb is a hard failure even when bun.lock now holds the recorded vendored tuples.
[agent] Found by the scheduled Bun bug-hunt routine (ledger #306).
Summary
Vendored mode records
bun.lockbwiring (kind: bun_lockb_package,file: bun.lockb) in.socket/vendor/state.json. Bun's documented binary → text migration,bun install --save-text-lockfile, deletesbun.lockband writesbun.lock. The vendored tuples carry over ("pk-a": ["pk-a@.socket/vendor/npm/<uuid>/pk-a-1.0.0.tgz", {}, "sha512-…"]), but socket-patch still treats the entry as abun.lockbentry. Every unwind path then breaks:vendor --revertandrollbackexit 1 withrevert_failed: "bun.lockb is missing; cannot safely revert the binary lock".scan --mode hosted(the takeover) exits 0 withredirected: 0,redirect_vendored_revert_failed, and the remedy "runsocket-patch vendor --revertto clean up". That remedy is the command that just failed.repairand a vendored re-run reportsuccessand change nothing. The only way out isgit checkout bun.lockb, which undoes the migration.bun.lock(exit 0). Because the previous wiring was abun.lockbrecord,carry_forward_wiringcan't match it, so the newbun.lockrecord hasoriginal: nulland the registry original is lost.vendor --revertthen exits 0 withstatus: success,vendor_lock_entry_drifted("has no recorded pre-vendor original; left as-is (runbun installto re-resolve it from the registry)"),vendor_artifact_keptandvendor_revert_kept. The lock still pins the vendored tarball, and the advisedbun installkeeps it, because the lock entry satisfiespackage.json. The project stays patched with no supported way back.Hosted mode is fine: a hosted
bun.lockbmigrated tobun.lockrolls back correctly, because hosted rollback re-derives the record from the registry.Impact
bun.lockband then followed Bun's own advice to move to the text lock (Bun ≥ 1.2) can no longer revert, roll back or switch to hosted.vendor --revertreports success (exit 0) while the patched artifact stays wired into the lock and installed. The pre-vendor registry tuple is permanently gone from the state.Repro
Uses a local mock of the patch API and npm registry: the same routes as
e2e_redirect_bun_build.rs(patches/batch,by-package,view,packagegrant, hosted tarball), with the registry on a different origin fromSOCKET_PATCH_SERVER_URL. Any vendored-capable patch reproduces it.Expected vs actual
docs/testing/bun-compatibility.md(Binarybun.lockbrow) promises "vendor --revertreturns the pre-hosted lock", and the vendored section says rollback "restores the original manifest / lock bytes, removes the.socket/vendorstate". The vendor state holds everything needed to restore the registry tuple: name, version, the registry resolution and the integrity in thebun_lockb_packageoriginal. The text-lock path already restores exactly this shape on a native text lock. If socket-patch must refuse, it should name a remedy that works, and the hosted takeover shouldn't exit 0 with a circular hint. A re-vendor should never discard a recorded original just because the lock changed format.Matrix (Linux, main
045d7ec)bun.lockbwriter--save-text-lockfilebun install)saveTextLockfile = false)saveTextLockfile = false)bun.lockfrom the start, 1.4.2bun.lockbnot migrated, writers 1.1.45 / 1.4.2bun install)macOS/Windows: untested (the code paths are OS-independent).
First bad version: not a regression. Release 4.0.0 refused
--mode vendoredonbun.lockb(exit 1), so the migration never arose there.Suspect code
crates/socket-patch-core/src/vendor/state.rs:453-473,carry_forward_wiring: an original is carried forward only from a previous record with a matching surface (wiring_surface_matches), so abun_lockb_package/bun.lockboriginal never reaches the newbun_lock_package/bun.lockrecord.crates/socket-patch-core/src/vendor/bun_lock.rs:536: rewriting "our own" vendored tuple recordsoriginal: Noneby design, relying on that carry-forward.crates/socket-patch-core/src/vendor/bun_binary.rs:419-426,revert: a missingbun.lockbis a hard failure even whenbun.locknow holds the recorded vendored tuples.