Skip to content

Fix Bun rewiring dropping the project's bun patch (#367) - #873

Open
Mikola Lysenko (mikolalysenko) wants to merge 9 commits into
mainfrom
agent/fix-bun-patched-dependencies-gate
Open

Mikola Lysenko (mikolalysenko) wants to merge 9 commits into
mainfrom
agent/fix-bun-patched-dependencies-gate

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Fixes #367

Summary

A project that patches a dependency itself with bun patch no 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 later bun install silently dropped the user's patch.

Root cause

bun patch --commit records patchedDependencies keyed on the registry name@version, in the root package.json and mirrored at the top of a text bun.lock. Bun applies the patch only while the lock resolves the package to that registry name@version. The hosted rewriters (rewrite_bun_lock and rewrite_bun_binary) and the vendored Bun backends (vendor/bun_lock.rs and vendor/bun_binary.rs) moved that entry to a hosted URL or a .socket/vendor tarball without reading patchedDependencies. Bun then stops applying the user's patch, and install still exits 0.

Changes

  • Shared reader (vendor/bun_lock_text.rs): patched_dependency_keys merges the keys from the root manifest and from bun.lock's mirror. patched_dependency_key matches the exact name@version, scoped names included, and also a bare name so that a key without a version can't slip past. patched_dependency_detail is the one user-facing message both modes use.
  • Hosted, bun.lock: rewrite_bun_lock skips the dep through skip_bun_user_patched and warns redirect_bun_patched_dependency_skipped with the key and a remedy. It also adds the uuid to bundled_skipped_uuids, so the in-run VEX never assumes the patch applied. Other packages in the lock are still rewired.
  • Hosted, bun.lockb: the engine now reads the root package.json as advisory input for a binary-only project. It removes user-patched deps from the overrides passed to rewrite_bun_binary, with the same warning and VEX exclusion.
  • Vendored, bun.lock and bun.lockb: read_project loads the keys and preflight_package refuses with vendor_lock_entry_unsupported before any write. The download plan's preflight_packages goes through the same gate, so it refuses before any download.
  • Docs: a new row in docs/testing/bun-compatibility.md and a redirect_bun_patched_dependency_skipped row in CLI_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 patchedDependencies entry 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

Issue / path Regression test Without fix With fix
#367 hosted bun.lock (manifest key, lock mirror, both; other-version key does not gate) patch::redirect::tests::bun_lock_user_patched_dependency_is_left_alone_loudly FAILED ok
#367 hosted bun.lockb (engine reads manifest, skips + warns + no VEX assume) hosted::engine::tests::issue_367_bun_lockb_keeps_a_user_patched_package_on_the_registry FAILED ok
#367 vendored bun.lock (loop + download preflight agree, nothing written) vendor::bun_lock::tests::user_bun_patch_refuses_before_any_write FAILED ok
#367 vendored bun.lockb vendor::bun_lock::tests::binary_user_bun_patch_refuses_before_any_write FAILED ok
key reader vendor::bun_lock_text::tests::patched_dependency_keys_read_manifest_and_lock n/a ok

For the red runs I made patched_dependency_key always return None: all 4 regression tests failed, and they pass with the real matcher.

Real Bun 1.3.14 check: bun patch --commit writes 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-ignored with 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)

  • The engine now reads the root package.json beside either Bun lock. Before, a text bun.lock reached the manifest only through a parseable workspaces section.
  • A user-patched Bun dep is recorded in the new RewriteResult::refused_bun_uuids and is never confirmed, even when a sibling package-lock.json takes the hosted URL.
  • Regression hosted::engine::tests::issue_367_bun_lock_user_patched_package_is_never_confirmed: fails at the manifest-read assertion without the first fix, and at confirmed.is_empty() without the second. Passes with both. 129 Bun-area core tests pass, and clippy -D warnings is clean.

Golden stability (e5c0c35)

The new refused_bun_uuids set 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)

  • Merged main to clear a conflict that was only in two re-blessed goldens. Main's versions pass with this branch: 48/48 equivalence tests.
  • main is red on coverage (utils::digest::tests::production_digests_go_through_the_helpers flags 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.
  • On e5dfad6, coverage, clippy and test (macOS, Windows) were green. Six Ubuntu Bun-compat cells failed with HTTP Error 503 from 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 CodeQL Analyze (rust) runner received a shutdown signal. The next push re-runs both.

Bugbot round 2 (fixed in 0b6121c)

  • The patchedDependencies reader now accepts a JSONC package.json (comments, trailing commas), as Bun does. Before, it found no keys there, which mattered most for bun.lockb projects that have no lock mirror. The JSONC case in patched_dependency_keys_read_manifest_and_lock fails with the fallback disabled and passes with it.
  • (21da460) A leading UTF-8 BOM is stripped before parsing, using utils::serde::strip_bom. The BOM case in the same test fails without the strip.

Wrappers under npm/, pypi/ and gem/ 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 (patchedDependencies in root package.json and/or mirrored in bun.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 in refused_bun_uuids so in-run VEX and sibling-lock confirmation never treat them as patched. bun.lockb-only projects now read root package.json for this gate; text bun.lock uses the same skip path via skip_bun_user_patched.

Vendored mode refuses the package at preflight with vendor_lock_entry_unsupported before any write or download (text and binary locks).

Docs add the warning code to CLI_CONTRACT.md and a compatibility row in docs/testing/bun-compatibility.md. A small refactor routes Gradle/JVM SHA-1/SHA-256 hashing through utils::digest helpers (aligned with coverage expectations on main).

Reviewed by Cursor Bugbot for commit 21da460. Configure here.


Generated by Claude Code

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
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 5, 2026 17:28
@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/hosted/engine.rs
Comment thread crates/socket-patch-core/src/patch/redirect/mod.rs
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
@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.

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
@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.

…-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)
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] Merged main (9c43dfc) to clear the merge conflict. The conflict was only the two re-blessed rewrite goldens, and main's versions pass with this branch's code: all 48 equivalence tests are green.

main itself is red on coverage: utils::digest::tests::production_digests_go_through_the_helpers flags crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs from the Gradle merge. This PR doesn't cause it. I ported #878's fix (659ac2c, cherry-picked as e5dfad6); it becomes a no-op once #878 lands. Locally the core lib passes apart from the 4 chmod-based tests that can't fail as root in this sandbox, and clippy -D warnings is clean.


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.

Stale Bugbot comment from a previous run.

Comment thread crates/socket-patch-core/src/vendor/bun_lock_text.rs
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
@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_text.rs
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
@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 21da460. Configure here.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] coverage on 21da460 failed because its runner received a shutdown signal (exit 143) in the middle of vendor_ledger_schema_e2e. Every test that ran before that passed, so this is runner loss, not a test failure. I'll re-run that job once, after the rest of CI run 37365013291 finishes; GitHub won't allow a re-run while it is still queued.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[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 coverage in CI run 37365013291, once those runs finish. The CodeQL default-setup run (37365009438) refuses an API re-run (403), so it will run again on the next push.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[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 coverage, which lost its runner partway through and failed rather than being cancelled.

  • I re-ran the cancelled jobs once each in CI (including coverage) and the Bun, Composer, PDM, Poetry, vlt, Go and Gradle compatibility runs.
  • Audit GHA Workflows, npm hosted/vendored, Benchmarks and pnpm hosted have already used their one re-run, and those jobs were cancelled the same way, without a runner. They need a re-run from a maintainer, or they will run again on the next code push.
  • The CodeQL default-setup run (37365009438) can't be re-run through the API.

Generated by Claude Code

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

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review at 21da460 (21da4608ea14a9d076b32594646f0aca93e65a78).

  • CI: 533/533 workflow checks green on the head commit (6 skipped by matrix rule), after re-running jobs the GitHub Actions runner outage cancelled. No test failed. The only non-green entries are 4 CodeQL default-setup Analyze jobs that GitHub cancelled during the outage, and GitHub doesn't allow re-running them ("This workflow run cannot be retried").
  • The gradle 6.9.4 / jdk 11 / vendor / macos-latest leg hit its 60-minute timeout during the outage. This PR is Bun-only. It passed on re-run.
  • Bugbot: reviewed 21da460 with no new issues, and no review threads are open.
  • Mergeable against main, with no conflicts.

Generated by Claude Code

This branch has not been deployed

No deployments
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