Skip to content

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/config setting or env BUNDLE_DEPLOYMENT=true), which installs into vendor/bundle;
  • simulate_version 5 on Bundler 4.x, which installs into .bundle/;
  • default_install_uses_path true on 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

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 install reuses it without refetching … prefer switching this project to a project-local bundle path (bundle config set --local path vendor/bundle, then bundle install) …

Expected vs actual

  • Expected: CLI_CONTRACT.md "Gem stale-install guard" says the gem env homes count only when Bundler uses system gems, "since with such a path bundle install fetches non-default gems into it and never reuses a system copy". Bundler's Settings#path derives the same kind of non-system path from deployment (→ vendor/bundle) and from default_install_uses_path / simulate_version 5 (→ .bundle) whenever no tier sets path / path.system / disable_shared_gems. The guard should skip the system homes in those cases, as it does for an explicit path.
  • 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


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

  1. mikolalysenko commented on Oct 8, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged as priority:p1 (Bundler). I confirmed the suspect code on main (05fd82b). In RubyCrawler::bundler_install_homes (ruby_crawler.rs:656), uses_system_gems is !default_root_has_stores && !bundler_sets_explicit_path(..), and bundler_sets_explicit_path (:1204) reads only path / path.system / disable_shared_gems. So deployment, default_install_uses_path and simulate_version 5 aren't modelled.

    This isn't a duplicate. #1001/#1002 covered the explicit path case only. It's related to #1098 (standalone gem vex judging the system-home copy), but the two are different code paths: #1098 is about vex not using bundler_install_homes at all, and this issue is about that function's own "uses system gems" test. A fix for #1098 that routes vex through bundler_install_homes should land after, or together with, this one, so it doesn't inherit the same false not_applied.


    Generated by Claude Code

  2. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    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.

  3. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [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 standalone vex still judge unused gem env homes). Branch: agent/fix-gem-bundler-system-homes. Claim-ID: 2026-10-09T15:21:58Z-105ae7


    Generated by Claude Code

  4. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #1290


    Generated by Claude Code

  5. mikolalysenko commented on Oct 9, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] #1290 fixes the deployment part of this issue (local config, env BUNDLE_DEPLOYMENT, global config). It does not fix the two .bundle-default variants. In Bundler 4.0.18, Settings::Path#use_system_gems? reads only bundler_5_mode? (simulate_version 5) and never default_install_uses_path. In 2.5.22 it reads only the default_install_uses_path settings flag, and FeatureFlag ignores simulate_version. So whether the system home is used depends on which Bundler runs, and a scan can't read that (BUNDLED WITH records 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 to bundle --version from the project root to learn the running Bundler. Leaving this issue open and labeled agent:needs-human.


    Generated by Claude Code

  6. added and removed
    v5-blockerMust resolve before v5: public interface/migration or ordinary patch-install-undo failure.
    on Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions