Repository navigation
Hosted gem stale-install guard still flags an unused system gem-home copy under Bundler deployment or the .bundle default path, so scan --mode hosted --vex fails with no_applicable_patches on fresh checkouts #1109
Description
[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).
Summary
#1002 fixed #1001 only for an explicit path setting. Bundler also stops using system gems in three other ways:
deployment true(the.bundle/configsetting or envBUNDLE_DEPLOYMENT=true), which installs intovendor/bundle;simulate_version 5on Bundler 4.x, which installs into.bundle/;default_install_uses_path trueon Bundler 2.x, which also installs into.bundle/.
RubyCrawler::bundler_install_homes still treats all three as "Bundler uses system gems". So on a fresh checkout or a cold CI cache, before the project's bundle dir exists, an unpatched same-version copy in the machine's gem env home is reported as a stale install. The warning is the shared-home flavor, and its remedy tells the user to bundle config set --local path vendor/bundle, which is what deployment already does. The flagged purl is then dropped from the same run's --vex. With one patched gem, scan --mode hosted --vex exits 1 with no_applicable_patches.
Impact
- A deployment-mode project (the standard CI and production setup) can't get an embedded VEX from
scan --mode hosted --vexon any machine whose shared gem home holds an old copy of the patched gem. - The warning is false, and its remedy is wrong. The behaviour fails closed (no false attestation), but it's the same false negative 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 described.
Proof with real Bundler (Ruby 3.3.6)
The system copy is never used in any of these shapes:
export GEM_HOME=$PWD/gh GEM_PATH=$PWD/gh
gem install colorize -v 0.8.1 --no-document # the shared-home copy
mkdir proj && cd proj
printf 'source "https://rubygems.org"\ngem "colorize", "0.8.1"\n' > Gemfile
bundle _4.0.18_ lock
bundle _4.0.18_ config set --local deployment true # or BUNDLE_DEPLOYMENT=true, or simulate_version 5
bundle _4.0.18_ install
bundle _4.0.18_ exec ruby -e 'puts Gem.loaded_specs["colorize"].full_gem_path'| Setting | Bundler | Installs (fetches) into, and loads from |
|---|---|---|
local deployment true |
4.0.18 | proj/vendor/bundle/ruby/3.3.0/gems/colorize-0.8.1 |
env BUNDLE_DEPLOYMENT=true |
2.5.22 | proj/vendor/bundle/ruby/3.3.0/gems/colorize-0.8.1 |
local simulate_version 5 |
4.0.18 | proj/.bundle/ruby/3.3.0/gems/colorize-0.8.1 |
socket-patch repro (main 05fd82b)
I used a throwaway test in crates/socket-patch-cli/tests/e2e_redirect_gem_stale_install.rs. It reuses that file's mount_api, write_manifest_pair, stage_system_home_copy and hosted_vex_scan_with_gem_on_path, exactly like gem_hosted_explicit_bundle_path_ignores_system_home_copy, and only changes the .bundle/config / env setting. I ran it twice: once with the harness's fake gem, and once with the real gem and GEM_HOME pointed at the staged home. Both runs gave the same results:
Case (.bundle/config or env; nothing installed in the project yet) |
stale warnings | --vex attests |
exit |
|---|---|---|---|
control: BUNDLE_PATH: "vendor/bundle" |
0 | yes | 0 |
BUNDLE_DEPLOYMENT: "true" |
1 | no | 1 (no_applicable_patches) |
env BUNDLE_DEPLOYMENT=true |
1 | no | 1 |
BUNDLE_SIMULATE_VERSION: "5" |
1 | no | 1 |
BUNDLE_DEFAULT_INSTALL_USES_PATH: "true" |
1 | no | 1 |
| control: no setting (Bundler really uses system gems) | 1 | no | 1 (correct) |
Warning emitted for the deployment case:
pkg:gem/stale-probe-gem@1.0.0 was switched to its hosted patch, but a stale UNPATCHED install is materialized in the shared gem home at …/system-home/gems/stale-probe-gem-1.0.0 —
bundle installreuses it without refetching … prefer switching this project to a project-local bundle path (bundle config set --local path vendor/bundle, thenbundle install) …
Expected vs actual
- Expected: CLI_CONTRACT.md "Gem stale-install guard" says the
gem envhomes count only when Bundler uses system gems, "since with such apathbundle installfetches non-default gems into it and never reuses a system copy". Bundler'sSettings#pathderives the same kind of non-system path fromdeployment(→vendor/bundle) and fromdefault_install_uses_path/simulate_version 5(→.bundle) whenever no tier setspath/path.system/disable_shared_gems. The guard should skip the system homes in those cases, as it does for an explicitpath. - Actual: the guard models only an explicit
path. Its "system gems" test is!default_root_has_stores && !bundler_sets_explicit_path(..), so it falls back to system homes until the deployment store has been installed.
OS × version
| OS | Bundler | Result |
|---|---|---|
| Linux | 4.0.18 (deployment, simulate_version 5), 2.5.22 (env deployment): real Bundler confirms the system copy is unused | guard fails on 05fd82b (the logic is OS- and version-independent) |
| macOS / Windows | — | not probed; same code path |
First bad version
This isn't a regression. The guard judged every gem env home before #1002 (v4.0.0 included). #1002 (f23fd82) narrowed that for an explicit path only.
Suspect code
crates/socket-patch-core/src/crawlers/ruby_crawler.rs:655-675(bundler_install_homes:uses_system_gemsignoresdeployment,default_install_uses_pathandsimulate_version).crates/socket-patch-core/src/crawlers/ruby_crawler.rs:1204-1229(bundler_sets_explicit_pathreads onlypath/path.system/disable_shared_gems).discover_bundle_stores_impl(ruby_crawler.rs:~452) already knows about the.bundledefault (Gem crawler ignores Bundler's.bundledefault install path (default_install_uses_pathon 2.x,simulate_version 5on 4.x), so agentapplypatches the system copy andvexattestsnot_affectedwhile Bundler loads the unpatched.bundle/ruby/<abi>copy #967), but only probes it once it has been installed.- Related: Gem VEX judges an unused system gem-home copy when the project sets a Bundler path, so standalone vex never attests #1098 proposes making
bundler_install_homesthe single answer for standalonevextoo. With this gap, that fix would inherit the same falsenot_appliedfor deployment projects.
Backlog review — 2026-10-08
Priority: P1 → P2. Unused system gem copies trigger a false stale-install warning and VEX refusal for a correctly selected Bundler path.
Activity
mikolalysenko commentedon Oct 8, 2026
[agent] Triaged as
priority:p1(Bundler). I confirmed the suspect code onmain(05fd82b). InRubyCrawler::bundler_install_homes(ruby_crawler.rs:656),uses_system_gemsis!default_root_has_stores && !bundler_sets_explicit_path(..), andbundler_sets_explicit_path(:1204) reads onlypath/path.system/disable_shared_gems. Sodeployment,default_install_uses_pathandsimulate_version 5aren't modelled.This isn't a duplicate. #1001/#1002 covered the explicit
pathcase only. It's related to #1098 (standalone gemvexjudging the system-home copy), but the two are different code paths: #1098 is aboutvexnot usingbundler_install_homesat all, and this issue is about that function's own "uses system gems" test. A fix for #1098 that routesvexthroughbundler_install_homesshould land after, or together with, this one, so it doesn't inherit the same falsenot_applied.Generated by Claude Code
mikolalysenko commentedon Oct 9, 2026
v5 release blocker (P1). The documented scan/install/VEX flow must work on a normal Bundler deployment checkout without being blocked by an unused system gem copy.
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 #1098; 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
mikolalysenko commentedon Oct 9, 2026
[agent] #1290 fixes the
deploymentpart of this issue (local config, envBUNDLE_DEPLOYMENT, global config). It does not fix the two.bundle-default variants. In Bundler 4.0.18,Settings::Path#use_system_gems?reads onlybundler_5_mode?(simulate_version 5) and neverdefault_install_uses_path. In 2.5.22 it reads only thedefault_install_uses_pathsettings flag, andFeatureFlagignoressimulate_version. So whether the system home is used depends on which Bundler runs, and a scan can't read that (BUNDLED WITHrecords who wrote the lock, see #751). Skipping the system home on either flag could attest a copy Bundler actually loads, so #1290 keeps judging it (fail closed) and pins that with regression tests. Closing the rest needs a maintainer decision, for example whether socket-patch may shell out tobundle --versionfrom the project root to learn the running Bundler. Leaving this issue open and labeledagent:needs-human.Generated by Claude Code