Repository navigation
Gem VEX judges an unused system gem-home copy when the project sets a Bundler path, so standalone vex never attests #1098
Description
[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: register comment.
Kind: bug (inconsistent logic within one ecosystem). Source: new finding; register E90.
Problem
The question "which gem homes does Bundler load this project's gems from" now has two answers on main @ e61a845:
RubyCrawler::get_gem_paths(ruby_crawler.rs#L43-L58).It appends every `gem env` home whenever the default `vendor/bundle` holds no store ([`#L136-L143`](https://github.com/SocketDev/socket-patch/blob/e61a8458651f78a8d42e4aad437e66a9f9894f6b/crates/socket-patch-core/src/crawlers/ruby_crawler.rs#L136-L143)).`` It does this even when the project sets an explicit Bundlerpath, where Bundler disables shared gems. This list feeds scan discovery, agentapply, andvex's installed-copy lookup (ecosystem_dispatch.rs#L618-L660).``RubyCrawler::bundler_install_homes(#L645-L690).`` It was added by Fix gem stale-install guard home selection (#1001, #729) #1002 for Hosted gem stale-install guard flags an unused system gem-home copy when the project sets a Bundlerpaththat isn't installed yet, soscan --mode hosted --vexfails withno_applicable_patcheson fresh checkouts #1001 and counts thegem envhomes only when Bundler uses system gems: no deployment store, and no explicit `path` according to `bundler_sets_explicit_path`. Only the hosted stale-install guard uses it (`scan/hosted.rs#L379`).
For a hosted gem purl, vex requires every copy in list 1 to verify (vex_consumed.rs#L28, vex.rs#L624-L630). #1002 fixed the #1001 symptom for scan --mode hosted --vex, but the standalone vex command still has it.
Proved by execution. I ran a throwaway test twice on e61a845. It reuses the harness of e2e_redirect_gem_stale_install.rs: stage_system_home_copy provides a fake gem env gemdir home holding the unpatched stale-probe-gem-1.0.0, plus the mock API. The steps were:
.bundle/configsetsBUNDLE_PATH;scan --mode hosted --vexexits 0, with no stale warning, as Hosted gem stale-install guard flags an unused system gem-home copy when the project sets a Bundlerpaththat isn't installed yet, soscan --mode hosted --vexfails withno_applicable_patcheson fresh checkouts #1001 intends;- the lock is then converged (a separate patch-registry
GEMsection); - finally, standalone
vexruns with the samePATH.
| case | standalone vex |
|---|---|
fresh checkout, BUNDLE_PATH: vendor/bundle (nothing installed yet) |
exit 1, not_applied ("the patched files still hold the original content"), no_applicable_patches |
installed: BUNDLE_PATH: gems, with the patched copy in gems/ruby/3.3.0/gems/ |
exit 1, not_applied |
| control: same as the row above, with the system-home copy removed | exit 0, verified, not_affected |
In both failing cases, Bundler never loads the system-home copy (the premise of #1001 and #1002). The project is patched, or about to be, yet vex refuses it on every run. On the fresh checkout, the unused copy also prevents the hosted lockfile basis from applying, because the copy counts as "installed".
The agent apply path reads the same list 1. In an explicit-path project, it therefore also writes to (and rollback restores) a same-version copy in the shared gem home, which Bundler doesn't load for that project. I found this by reading the code; I didn't execute it.
Symptoms
- Hosted gem stale-install guard flags an unused system gem-home copy when the project sets a Bundler
paththat isn't installed yet, soscan --mode hosted --vexfails withno_applicable_patcheson fresh checkouts #1001 / Fix gem stale-install guard home selection (#1001, #729) #1002: the same defect in the stale guard, fixed there only. - Hosted gem scan pins a version that only another project installed into the shared gem home, rewriting
gem "x", "~> 1.0"to the older patched"0.8.1", so the prescribedbundle installdowngrades the project's locked gem #1055: also shared-gem-home state leaking into a project's answer (version selection; different path).
Impact: fail-closed (no false attestation), but a false negative. Any developer or CI machine whose system gem home holds an old copy of a patched gem never gets vex output for that gem, in projects that set path (common in CI caches, bundle config set --local path vendor/bundle). Size: one model function plus three call sites.
Proposed change
- Make
bundler_install_homes(or oneBundlerHomes { loaded, default_gem_homes }value built once) the single answer to "which homes does Bundler load for this project". - Make the gem branch of the
vexinstalled lookup (find_manifest_package_copies_reusingandhosted_consumed_copies) judge only those homes. - Leave
get_gem_pathswith only its documented default-gem reason for keeping thegem envhomes under an explicit path. Either restrict those homes to gems whose spec is underspecifications/default/, or keep them for apply only. The PR decides which and documents it in CLI_CONTRACT's gem section. - Delete the duplicated tier read:
bundler_install_homesre-reads the app, env and global tiers thatdiscover_bundle_stores_implhas already resolved.BundleStoreDiscoveryshould carryexplicit_pathso the rule lives in one place.
Size and scope
crawlers/ruby_crawler.rs,ecosystem_dispatch.rs(the gem branch) andcommands/vex_consumed.rs(the gem row), plus tests.- About 150–250 production lines.
- Out of scope: agent
applywrite targets beyond the default-gem rule, and Hosted gem scan pins a version that only another project installed into the shared gem home, rewritinggem "x", "~> 1.0"to the older patched"0.8.1", so the prescribedbundle installdowngrades the project's locked gem #1055's version selection.
Acceptance criteria
- Standalone
vexattests the gem in both failing rows above, and in eachBUNDLE_PATHspelling (env,.bundle/config, global config). Add a regression e2e test besidegem_hosted_explicit_bundle_path_ignores_system_home_copy. - Without a Bundler
path(system gems), an unpatchedgem envcopy still blocks attestation (not_applied), and so doespath.system: trueoutranking envBUNDLE_PATH. This mirrorsgem_hosted_system_install_still_flags_system_home_copy. - Hosted gem VEX attests
not_affectedfor an unpatched install when.bundle/configsets an out-of-treepath(absolute or~/…), because the skipped bundle root counts as "nothing installed" #709's out-of-tree config root is still read for verification. - The stale guard and
vextake the home list from the same function: a unit test asserts that they agree on each tier combination. -
e2e_redirect_gem_stale_install, the ruby crawler unit tests and the gem VEX discovery tests stay green.
Dependencies
- None blocking.
- Touches the same crawler as Gem agent crawl ignores that
BUNDLE_GEMFILE=gemfiles/x.gemfilemovesBundler.root, so a relative bundle path resolves to the wrong dir,applypatches the system copy andvexattestsnot_affectedwhile Bundler loads the unpatchedgemfiles/vendor/bundlecopy #952 (BUNDLE_GEMFILEandBundler.root) and PR Fix hosted gem pinning a version the lock doesn't resolve (#1055) #1060 (Hosted gem scan pins a version that only another project installed into the shared gem home, rewritinggem "x", "~> 1.0"to the older patched"0.8.1", so the prescribedbundle installdowngrades the project's locked gem #1055). Rebase on whichever lands first. - Related to the E40 tracking (Tracking: move vex_consumed's per-ecosystem consumed-copy rules from the CLI into core #855): per-ecosystem consumed-copy rules should live in core.
Backlog review — 2026-10-08
Priority: P1 → P2. An unused system gem copy makes VEX reject a correctly patched Bundler path; false negative.
Activity
audit-ecosystems: file E46 decision (#1099) and E90 (#1098); refresh …
mikolalysenko commentedon Oct 9, 2026
[agent] Re-checked on main @
f3c6313(architecture audit, ecosystems and formats). This still holds, and the two answers are unchanged:get_gem_pathsstill appends everygem envhome whenever the default root has no store and a Bundler manifest exists (ruby_crawler.rs#L135-L141),`` whateverbundler_sets_explicit_pathsays.bundler_install_homes(#L644) is still used only by the hosted stale guard (scan/hosted.rs#L368).The comment above the fallback gives a reason to keep the system homes: default gems (
rexml,json) ship only there. So the fix should keep the default-gem homes and drop only the non-defaultgem envhomes under an explicitpath, rather than reusebundler_install_homeswholesale.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
v5 release blocker (P1). VEX must inspect the Bundler home actually used by an ordinary project with an explicit install path, not reject it because of unrelated system gems.
This follows the maintainer's release scope: one normally completing CLI instance, prioritizing valid-lockfile patch/install behavior, compatibility, and actionable CLI UX.
mikolalysenko commentedon Oct 9, 2026
[agent] Claiming this issue (with #1109; shared root cause: the gem crawler has no single model of whether Bundler uses system gems, so
deployment/.bundle-default projects and standalonevexstill judge unusedgem envhomes). Branch: agent/fix-gem-bundler-system-homes. Claim-ID: 2026-10-09T15:21:58Z-105ae7Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
[agent] Draft PR: #1290
Generated by Claude Code
Test gem system-home cases #1098 #1109 hit
Skip unused system gem homes in gem checks