Skip to content

Fix vex remedy for a vendored gem leftover (#2076) - #2081

Open
Mikola Lysenko (mikolalysenko) wants to merge 6 commits into
mainfrom
agent/fix-vex-vendored-gem-leftover-copy
Open

Mikola Lysenko (mikolalysenko) wants to merge 6 commits into
mainfrom
agent/fix-vex-vendored-gem-leftover-copy

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Nov 14, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-fable-5-2

Fixes #2076

Summary

After vendoring a gem, socket-patch vex warned vendored_tree_out_of_sync on
every run and told the user to re-run the package manager's install. With
Bundler that can never clear it: the lock takes the gem from the vendored
path, so bundle install installs nothing for it and leaves the old registry
copy in the gem home.

The warning stays (same code, same trigger, same verdict), but for a gem it
now says what is true and what removes the copy:

pkg:gem/rack@3.2.8: an installed copy of this gem does not match the vendored
artifact (/vendor/bundle/ruby/3.3.0/gems/rack-3.2.8). Bundler's lock
takes the gem from the committed .socket/vendor path, which the attestation is
based on, so a Bundler run of that lock loads the patched copy, and
bundle install neither replaces nor removes what an earlier install left in
a gem home. Code that loads the gem outside that Bundler setup still runs such
a copy (plain ruby, or RubyGems activating a default gem before Bundler
runs). bundle clean prunes it from the project's own bundle path; in any
other gem home, gem uninstall it there if no other project uses it (a
default gem that ships with Ruby cannot be uninstalled).

Every other ecosystem's text is byte-identical to main.

Root cause

vex.rs had one wording for every ecosystem ("the live tree carries different
bytes — re-run your package manager's install"), with addenda for Hatch and
PDM, the two managers known not to resync on install. Bundler is a third, and
had none.

What changed

  • crates/socket-patch-core/src/vex/verify.rs: the drift probe records which
    copy drifted (VerifyOutcome::vendored_out_of_sync_copies). For a gem it
    records every drifted copy, since each gem home needs its own cleanup; other
    ecosystems still stop at the first. vendored_out_of_sync is unchanged.
  • crates/socket-patch-cli/src/commands/vex.rs: vendored_out_of_sync_detail
    builds the text. A gem gets the wording above and no Hatch/PDM notes.
  • CLI_CONTRACT.md: the two places that describe this warning.

Why the warning is not simply dropped for gems

The issue offers two fixes: stop counting the leftover, or fix the wording.
The first commit on this branch did the former, and /code-review showed why
that is unsafe: unlike Go's module cache, a gem-home copy is reachable. Plain
ruby -rrack, a stale bundle install --standalone setup script, or another
Gemfile sharing the gem home all run the unpatched copy, and this warning is
the only disclosure. So the detection is exactly what main has.

The remedy is described rather than printed as a command for the same reason:
an earlier revision printed gem uninstall <name> -v <version> -i <home>, and
review reproduced it failing (dependents prompt, symlinked gem home) and noted
that it put purl text from committed files into a line meant to be pasted
into a shell. No part of the purl reaches the remedy text now; a unit test
pins that.

The branch history shows those steps (4 commits); the net diff is small.

Evidence

Red on main, real Bundler 4.0.18 / Ruby 3.3.6:

$ cargo test -p socket-patch-cli --test e2e_vendor_gem_build gem_vendor_fresh_checkout -- --ignored
Warning: pkg:gem/rack@3.2.8: the installed tree does not match its vendored artifact; ... re-run your package manager's install to resync it.
test gem_vendor_fresh_checkout_bundle_install_and_revert ... FAILED

Green on this branch:

Check Result
cargo test -p socket-patch-core --lib 7725 passed
cargo test -p socket-patch-cli --lib 944 passed
e2e_vendor_gem_build (--include-ignored, real Bundler) 14 passed
e2e_redirect_gem_build (--include-ignored) 42 passed
e2e_vex, e2e_vex_vendor, e2e_vex_redirect, e2e_embedded_vex, in_process_vendor all passed
cargo clippy --locked --workspace --all-features -- -D warnings (CI's form) clean
cargo fmt --check on the touched files clean

Tests for #2076:

  • e2e_vendor_gem_build::gem_vendor_fresh_checkout_bundle_install_and_revert:
    after vendoring, the warning names the leftover directory and bundle clean
    and no longer says to re-run the install; then bundle clean (on a copy of
    the project) removes the leftover, leaves Gemfile.lock byte-identical, and
    vex no longer warns.
  • commands::vex::tests::out_of_sync_detail_names_a_remedy_bundler_honors:
    gem wording, no purl text in the remedy, no pypi notes on a gem, other
    ecosystems unchanged.
  • vex::verify::tests::vendored_drift_probe_lists_every_drifted_gem_copy and
    the extended vendored_drift_probe_checks_every_installed_copy.

Notes for the reviewer

  • cargo clippy -p <crate> --all-targets fails locally on main too (44
    errors in test targets of files this PR does not touch, e.g.
    patch/jvm_jar.rs); none are in the touched files.
  • In a shared gem home that other projects use, the warning still repeats on
    every run, because there the copy really is on disk and loadable outside
    Bundler. Silencing it there would need a maintainer decision (for example
    an opt-out flag); not done here.
  • crates/socket-patch-cli/tests/e2e_redirect_gem_stale_install.rs and
    CLI_CONTRACT.md still say vex reports such a leftover, which remains true.

https://claude.ai/code/session_011f9aeT1WzjP49EBEYrCf1s


Generated by Claude Code

Assisted-by: Claude Code:claude-fable-5-2
Once Gemfile.lock takes a vendored gem from its PATH source,
Bundler loads the vendored directory and installs nothing for the
gem. The registry copy an earlier bundle install left in the gem
home stays, unpatched, until bundle clean. vex counted it as the
live tree and warned vendored_tree_out_of_sync on every run, with
a remedy (re-run the install) that could never clear it.

The probe now skips a vendored gem's gem-home copies, as it does
Go's module cache and Gradle's files-2.1 copies. A default gem's
copy still counts, since RubyGems can load it before Bundler sets
the load path. The verdict was never affected and is unchanged.

Fixes #2076

Assisted-by: Claude Code:claude-fable-5-2
This replaces the previous commit's approach. Dropping the
gem-home copy from the drift probe hid a copy that a load outside
Bundler still runs (plain ruby, a stale --standalone setup
script), so the probe is back to what main has.

What #2076 reports is the wording. For a vendored gem the warning
said the live tree carries different bytes and to re-run the
install. Bundler loads the vendored path and bundle install never
replaces the old registry copy, so the advice could not clear it.

The gem detail now says the copy is a leftover Bundler no longer
loads, and names bundle clean (a project bundle path) or
gem uninstall NAME -v VERSION (a shared gem home). The warning
code, the verdict and every other ecosystem's text are unchanged.

Fixes #2076

Assisted-by: Claude Code:claude-fable-5-2
Review of the previous commit found the gem wording wrong in three
cases: a default gem (no command removes it, and RubyGems can load
it before Bundler), a shared gem home (gem uninstall needs -i and
the platform), and a Hatch note appended to a gem's warning.

The drift probe now records which copy drifted, and the warning
names that directory and gives the remedy for where it is: bundle
clean inside the project, gem uninstall with -i and --platform
outside it, none for a default gem. A gem takes no pypi notes.

Refs #2076

Assisted-by: Claude Code:claude-fable-5-2
Review of the previous commit: choosing bundle clean by whether
the copy sits under --cwd broke on a relative --cwd, on a GEM_HOME
inside the project (bundle clean refuses there) and on a bundle
path outside it, and only the first drifted copy was named.

The gem warning now lists every drifted copy and gives one
gem uninstall NAME -v VERSION -i HOME per gem home, which removes
the copy from any home, a Bundler path included, and mentions
bundle clean for the project's own bundle path. The home is
shell-quoted and the platform percent-decoded. The text no longer
names Gemfile.lock, since the lock may be gems.locked.

Refs #2076

Assisted-by: Claude Code:claude-fable-5-2
Review of the previous commit: the printed gem uninstall command
stopped at the dependents prompt, failed through a symlinked gem
home, and carried purl text from committed files into a line meant
to be pasted into a shell.

The gem warning now lists the drifted copies and says in words
what removes one: bundle clean for the project's own bundle path,
gem uninstall in any other gem home no other project uses, and
nothing for a default gem. No part of the purl reaches the remedy.

Only a gem's probe collects every drifted copy; the others stop
at the first, as before. The e2e test runs bundle clean on a copy
of the project, so the revert leg keeps its starting tree.

Refs #2076

Assisted-by: Claude Code:claude-fable-5-2
@mikolalysenko Mikola Lysenko (mikolalysenko) changed the title Fix vex warning on a vendored gem leftover (#2076) Fix vex remedy for a vendored gem leftover (#2076) Nov 14, 2026
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review November 14, 2026 10:48
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


Generated by Claude Code

@cursor

cursor Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

PR Summary

Low Risk
User-facing warning text and richer copy enumeration only; vendored drift detection and VEX attestation logic are otherwise unchanged.

Overview
Fixes #2076: vendored_tree_out_of_sync still fires when a gem-home copy diverges from the vendored artifact, but for pkg:gem/* the warning no longer tells users to “re-run your package manager's install” (Bundler won’t touch that leftover when the lock points at .socket/vendor).

Verification now records vendored_out_of_sync_copies on VerifyOutcome. For gems, every drifted install path is captured (multiple gem homes); other ecosystems still stop at the first drifted copy.

CLI adds vendored_out_of_sync_detail() so gem warnings name those paths, explain Bundler vs out-of-band Ruby loads, and suggest bundle clean or gem uninstall (described, not built from the PURL). Hatch/PDM addenda stay on non-gem packages only.

Contract & tests: CLI_CONTRACT.md matches the new wording; e2e vendored-gem flow asserts the message and that bundle clean clears the warning; unit tests pin gem vs pypi behavior.

Reviewed by Cursor Bugbot for commit a27356d. Configure here.

@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 a27356d. Configure here.

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

[agent] PR shepherd: review brief for the approver

What it does. After a gem is vendored, socket-patch vex kept warning vendored_tree_out_of_sync and told the user to re-run their install, which Bundler never honours for a path-sourced gem. For gems the warning now names each drifted gem-home copy and gives remedies that actually clear it (bundle clean, or gem uninstall in a shared gem home). Detection, warning code and verdict are unchanged; every other ecosystem's text is byte-identical.

Risk: low. Warning wording plus one new field on VerifyOutcome; the only behaviour change is that the drift probe keeps scanning a gem's copies instead of stopping at the first. No attestation or exit-code change.

Look here

  • verify.rs:253: gems record every drifted copy, others still break at the first; the purl is pushed to vendored_out_of_sync once.
  • vex.rs:1827: vendored_out_of_sync_detail; falls back to the old text for non-gems or when no copy was recorded. No purl text reaches the remedy.
  • vex.rs:1328: call site filters copies by purl.
  • e2e_vendor_gem_build.rs:657 and :773: real-Bundler regression, then proof that bundle clean clears the warning and leaves Gemfile.lock byte-identical. Note this test is #[ignore] (needs Ruby/Bundler), so CI runs only the unit tests.

Verified. Read the full diff, the issue (#2076) and the description; they match. New unit tests pin gem vs. cargo copy listing and the gem wording (including a purl with a shell-looking qualifier). CHANGELOG.md untouched. CI green on a27356df (ci-ok, clippy); Bugbot found no issues; no open review threads.

Changes I made. None.

Open questions. None blocking. The description notes that in a gem home shared with other projects the warning still repeats each run (by design, since the copy is loadable there).


Generated by Claude Code

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Nov 14, 2026
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

2 participants