Fix Bun rewiring dropping the project's bun patch (#367) - #873
Mikola Lysenko (mikolalysenko) wants to merge 9 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Bun applies a patch from `bun patch` (package.json patchedDependencies, mirrored in bun.lock) only while the lock resolves the package to its registry name@version. Hosted and vendored mode moved that entry to a hosted URL or a vendored tarball, so every later install silently dropped the user's own patch while socket-patch reported success. Hosted mode now leaves such a package on its registry entry in both bun.lock and bun.lockb, warns redirect_bun_patched_dependency_skipped naming the key, and keeps the in-run VEX from assuming the Socket patch applied. Vendored mode refuses it with vendor_lock_entry_unsupported before any write or download. Other packages in the lock are still rewired. Fixes #367 Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
Bugbot review of #873 found two gaps in the bun patch guard. A text bun.lock only reached the root package.json through its workspaces section, so a lock without one never saw the patchedDependencies keys and rewired the package anyway. The manifest is now read beside either Bun lock. A package left on the registry could still be counted as switched when a sibling package-lock.json took the hosted URL, although Bun keeps installing the registry bytes. Such uuids are now recorded as refused and never confirmed. Refs #367 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
The refused-Bun uuid set added to RewriteResult changed the serialized and Debug output that the redirect equivalence goldens hash. The set is now left out of serialization when empty, like the other per-ecosystem uuid sets, and the two goldens that hash the Debug output (poetry, pdm) are re-blessed. Only their output digests change; every case and input digest is identical. Refs #367 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
…-dependencies-gate # Conflicts: # crates/socket-patch-core/tests/equivalence/pdm_rewrite_shared_parse.golden # crates/socket-patch-core/tests/equivalence/poetry_rewrite.golden
main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c)
|
[agent] Merged
Generated by Claude Code |
|
BugBot review Generated by Claude Code |
Bun accepts comments and trailing commas in package.json. The patchedDependencies reader parsed it as strict JSON, so such a manifest yielded no keys. A bun.lockb project has no text mirror to fall back on, so the project's own bun patch could still be rewired away. The reader now strips JSONC comments and trailing commas before parsing, leaving string contents untouched. Refs #367 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
A Windows-saved package.json can start with a UTF-8 byte order mark, which Bun ignores but serde_json rejects. The reader found no patchedDependencies keys in such a manifest, so a bun.lockb project could still lose its own bun patch. The mark is now stripped first, as the crate's other manifest readers do. Refs #367 Assisted-by: Claude Code:claude-opus-5-5
|
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 21da460. Configure here.
|
[agent] Generated by Claude Code |
|
[agent] CI on 21da460 lost runners across the board: about 60 jobs were cancelled at once (install-proof, build, composer, go, CodeQL and others), and none of them reported a test failure. I re-ran the failed jobs on the four runs that have finished (Audit GHA Workflows, npm hosted/vendored, Benchmarks, pnpm hosted). I'll re-run the rest, including Generated by Claude Code |
|
[agent] Status on 21da460: every non-green job is a cancellation, and none of them reported a test failure. The jobs waited about 16 minutes without being assigned a runner, then were cancelled with no steps run. The one exception is
Generated by Claude Code |
|
Burn-down agent: labeled Ready for review at
Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #367
Summary
A project that patches a dependency itself with
bun patchno longer loses that patch when socket-patch rewires Bun locks. Hosted mode leaves the package on its registry entry and warns. Vendored mode refuses before writing anything. Before this change, both modes reported success, and every laterbun installsilently dropped the user's patch.Root cause
bun patch --commitrecordspatchedDependencieskeyed on the registryname@version, in the rootpackage.jsonand mirrored at the top of a textbun.lock. Bun applies the patch only while the lock resolves the package to that registryname@version. The hosted rewriters (rewrite_bun_lockandrewrite_bun_binary) and the vendored Bun backends (vendor/bun_lock.rsandvendor/bun_binary.rs) moved that entry to a hosted URL or a.socket/vendortarball without readingpatchedDependencies. Bun then stops applying the user's patch, and install still exits 0.Changes
vendor/bun_lock_text.rs):patched_dependency_keysmerges the keys from the root manifest and from bun.lock's mirror.patched_dependency_keymatches the exactname@version, scoped names included, and also a barenameso that a key without a version can't slip past.patched_dependency_detailis the one user-facing message both modes use.bun.lock:rewrite_bun_lockskips the dep throughskip_bun_user_patchedand warnsredirect_bun_patched_dependency_skippedwith the key and a remedy. It also adds the uuid tobundled_skipped_uuids, so the in-run VEX never assumes the patch applied. Other packages in the lock are still rewired.bun.lockb: the engine now reads the rootpackage.jsonas advisory input for a binary-only project. It removes user-patched deps from the overrides passed torewrite_bun_binary, with the same warning and VEX exclusion.bun.lockandbun.lockb:read_projectloads the keys andpreflight_packagerefuses withvendor_lock_entry_unsupportedbefore any write. The download plan'spreflight_packagesgoes through the same gate, so it refuses before any download.docs/testing/bun-compatibility.mdand aredirect_bun_patched_dependency_skippedrow inCLI_CONTRACT.md.I chose to refuse instead of re-keying. Bun has no measured way to key a patch on a non-registry resolution, so carrying the user's patch forward would mean writing a lock shape nobody has tested. Refusing keeps the user's patch working, and the message says how to combine the two: fold the Socket fix into their own patch, or drop the
patchedDependenciesentry and re-run.Related but out of scope: #711 (npm 12's native
patchedDependencies). It's a different package manager and code path, and earlier triage deliberately kept it separate. The reader added here is Bun-specific.Test evidence
bun.lock(manifest key, lock mirror, both; other-version key does not gate)patch::redirect::tests::bun_lock_user_patched_dependency_is_left_alone_loudlybun.lockb(engine reads manifest, skips + warns + no VEX assume)hosted::engine::tests::issue_367_bun_lockb_keeps_a_user_patched_package_on_the_registrybun.lock(loop + download preflight agree, nothing written)vendor::bun_lock::tests::user_bun_patch_refuses_before_any_writebun.lockbvendor::bun_lock::tests::binary_user_bun_patch_refuses_before_any_writevendor::bun_lock_text::tests::patched_dependency_keys_read_manifest_and_lockFor the red runs I made
patched_dependency_keyalways returnNone: all 4 regression tests failed, and they pass with the real matcher.Real Bun 1.3.14 check:
bun patch --commitwrites exactly the keys and mirror the reader parses, scoped included ("@types/node@20.0.0": "patches/@types%2Fnode@20.0.0.patch"under"patchedDependencies": {in bun.lock).Commands run locally:
cargo clippy --workspace --all-features -- -D warnings: clean.cargo test -p socket-patch-core --all-features --lib: 5055 passed, 4 failed. The 4 failures (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_…) are chmod-based tests that can't fail as uid 0 in this sandbox. They are in untouched modules and run as non-root in CI.SOCKET_PATCH_BUN_E2E_REQUIRED=1 cargo test -p socket-patch-cli --all-features --test e2e_redirect_bun_build --test e2e_vendor_bun_build --test mode_migration_bun -- --include-ignoredwith real Bun 1.3.14: 16 + 14 + 14 passed.cargo test -p socket-patch-cli --all-features --test e2e_bun_lockb --test in_process_vendor_bun_takeover --test vendor_eject_bun_lockb -- --include-ignored: 13 + 22 + 5 passed.cargo fmt: the new hunks are rustfmt-clean. The repo isn't fmt-clean on main (130 files) and CI has no fmt step, so I didn't reformat untouched code.Bugbot round 1 (fixed in 71c0421)
package.jsonbeside either Bun lock. Before, a textbun.lockreached the manifest only through a parseableworkspacessection.RewriteResult::refused_bun_uuidsand is never confirmed, even when a siblingpackage-lock.jsontakes the hosted URL.hosted::engine::tests::issue_367_bun_lock_user_patched_package_is_never_confirmed: fails at the manifest-read assertion without the first fix, and atconfirmed.is_empty()without the second. Passes with both. 129 Bun-area core tests pass, and clippy-D warningsis clean.Golden stability (e5c0c35)
The new
refused_bun_uuidsset is skipped during serialization when empty, like the other per-ecosystem uuid sets, so the hashed rewrite goldens don't change.Merge with main and inherited failure (148ef3b, e5dfad6)
mainto clear a conflict that was only in two re-blessed goldens. Main's versions pass with this branch: 48/48 equivalence tests.mainis red oncoverage(utils::digest::tests::production_digests_go_through_the_helpersflags three Gradle files). I ported Route Gradle digests through utils::digest #878's fix (659ac2c → e5dfad6); it becomes a no-op once Route Gradle digests through utils::digest #878 lands.coverage,clippyandtest(macOS, Windows) were green. Six Ubuntu Bun-compat cells failed withHTTP Error 503from the patch service, all inside one 40 s window (18:55:41–18:56:20). Every cell that started after it passed, including 1.1.43 and 1.1.45 on the same lock formats. The CodeQLAnalyze (rust)runner received a shutdown signal. The next push re-runs both.Bugbot round 2 (fixed in 0b6121c)
patchedDependenciesreader now accepts a JSONCpackage.json(comments, trailing commas), as Bun does. Before, it found no keys there, which mattered most forbun.lockbprojects that have no lock mirror. The JSONC case inpatched_dependency_keys_read_manifest_and_lockfails with the fallback disabled and passes with it.utils::serde::strip_bom. The BOM case in the same test fails without the strip.Wrappers under
npm/,pypi/andgem/only dispatch to the binary, so they need no change.🤖 Generated with Claude Code
https://claude.ai/code/session_01UMfrUU5yGZgTxSnrSsKLoL
Note
Medium Risk
Changes Bun lock rewrite and vendored preflight semantics plus VEX confirmation rules; behavior is fail-closed (skip/refuse with warnings) but affects which deps are considered patched.
Overview
Fixes #367: dependencies the project patches via
bun patch(patchedDependenciesin rootpackage.jsonand/or mirrored inbun.lock) are no longer silently rewired to hosted URLs or vendored tarballs, which made Bun stop applying the user's patch on every install.Hosted mode detects those keys (including JSONC manifests and bare-name keys), leaves the lock entry on its registry tuple, emits
redirect_bun_patched_dependency_skipped, and tracks uuids inrefused_bun_uuidsso in-run VEX and sibling-lock confirmation never treat them as patched.bun.lockb-only projects now read rootpackage.jsonfor this gate; textbun.lockuses the same skip path viaskip_bun_user_patched.Vendored mode refuses the package at preflight with
vendor_lock_entry_unsupportedbefore any write or download (text and binary locks).Docs add the warning code to
CLI_CONTRACT.mdand a compatibility row indocs/testing/bun-compatibility.md. A small refactor routes Gradle/JVM SHA-1/SHA-256 hashing throughutils::digesthelpers (aligned with coverage expectations on main).Reviewed by Cursor Bugbot for commit 21da460. Configure here.
Generated by Claude Code