Skip to content

Lock inventory reads only Gemfile.lock, so a gems.rb project's gems.locked is invisible and a stale Gemfile.lock is read instead #736

Description

[agent] Filed by the scheduled architecture audit routine (ecosystems and formats). Register: discussion #560 register.

Kind: bug. Source: new finding, register E56 (related to review Part 5.4 on gem section models, E19).

Problem

Bundler loads one manifest/lock pair. When there is no BUNDLE_GEMFILE, that is gems.rb + gems.locked if gems.rb exists, and Gemfile + Gemfile.lock otherwise. socket-patch has a shared resolver for this, LoadedManifest::pair.`` Hosted mode uses it through keep_bundler_loaded_gem_files, which also works over a memory view. The vendored refusal (`gem_manifest_refusal`) and the crawler use it as well.

Two other readers each choose the lock their own way:

  • Lock inventory reads only Gemfile.lock: lock_inventory/gem.rs#L46-L53 (view.read_text("Gemfile.lock")). gem_remotes does the same.
  • VEX discovery reads both locks whatever Bundler loads: vex/discover/gem.rs#L133-L138 (for file in BUNDLER_LOCKS).

That makes three rules for one question.

Proof by execution (a unit probe at 045d7ec, run twice, not committed). The project has gems.rb (gem "rack", "2.2.8") and a valid gems.locked locking rack (2.2.8):

gems.locked only:                  inventory_project = []
same text via GemfileLock::parse:  ["pkg:gem/rack@2.2.8"]
bundler_loaded_manifest().pair():  ("gems.rb", "gems.locked")
+ stale Gemfile.lock (rack 2.0.0): inventory_project = ["pkg:gem/rack@2.0.0"]

Consumers that see the wrong set

  • The in-memory hosted engine builds its purl set from this inventory (hosted/memory/mod.rs#L571-L578).`` So for a gems.rb project it finds no gems, even though its own gem rewriter would edit `gems.locked`. With a leftover `Gemfile.lock`, it plans versions Bundler doesn't use.
  • Scan's lockfile supplement (scan/discovery.rs#L71-L95) and apply's lockfile_resolved (apply.rs#L2304-L2306) miss lockfile-only gems, or count stale ones.
  • VEX ledger liveness (vex/discover/mod.rs#L1611-L1613) judges a hosted gem pin against the wrong lock.

Symptoms

None filed. #341 and #390 fixed the same "wrong pair" class for the vendored and hosted writers but not for the readers.

Impact

Medium for gems.rb projects, which are Bundler's documented alternate spelling. For a stale-twin project it is incorrect data rather than missing data.

Proposed change

  • Move the pair selection that keep_bundler_loaded_gem_files does into formats::gem::manifest as loaded_lock(view: &ProjectView) -> Option<&'static str> (disk: bundler_loaded_manifest; memory: .bundle/config only, as today).
  • Make inventory_gemfile_lock_raw_in, gem_remotes and VEX discovery's extract read only that lock. VEX may still diagnose the ignored twin.
  • Delete the hard-coded "Gemfile.lock" reads in lock_inventory/gem.rs and the BUNDLER_LOCKS loop's both-locks rule.

Size and scope

4 files and under 120 changed production lines. Out of scope: the three gem section models (E19) and BUNDLE_GEMFILE pointing outside the root, which stays unsupported.

Acceptance criteria

  • inventory_project on a gems.rb + gems.locked project returns its gems.
  • With a stale Gemfile.lock beside gems.rb + gems.locked, inventory and VEX discovery read only gems.locked.
  • .bundle/config BUNDLE_GEMFILE: Gemfile beside a gems.rb reads Gemfile.lock (disk and memory views).
  • In-memory hosted engine regression: a gems.rb project yields a gem candidate.
  • Existing lock_inventory, vex::discover::gem, formats::gem::manifest and hosted gem tests stay green.

Dependencies

None. It touches files near open #712, #684 and #621 (gem settings), but not the same functions.

Activity

  1. added
    bugSomething isn't working
    arch-auditFiled by a scheduled architecture audit routine (see the architecture review discussion)
    on Oct 4, 2026
  2. mikolalysenko commented on Oct 4, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Triaged: priority:p1 (Bundler). Not a duplicate; no existing fix PR. #341 / #390 fixed the writer side of the same gems.rb/Gemfile pair selection; this is the reader side (lock inventory, gem_remotes, VEX discovery).


    Generated by Claude Code

  3. mikolalysenko commented on Oct 4, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Bundler bug-hunt routine (ledger #316): a real-install repro of the VEX-discovery half of this issue, at 045d7ec.

    Shape: the project moved from Gemfile to gems.rb. It has gems.rb + gems.locked (upstream colorize 0.8.1 from rubygems.org, with CHECKSUMS), plus a leftover Gemfile.lock holding the hosted redirect (remote: …/patch-registry/gem/…, colorize (= 0.8.1)!) from a hosted scan run while the project still used Gemfile. There is no Gemfile and no .socket/manifest.json.

    # gems.rb: source "https://rubygems.org"; gem "rake"; gem "colorize", "~> 0.8.1"
    bundle config set --local path vendor/bundle && bundle install && bundle lock --add-checksums && rm -rf vendor
    # Gemfile.lock = the redirected lock a `scan --mode hosted` wrote for the same deps as Gemfile/Gemfile.lock
    socket-patch vex --output pre.json --product pkg:generic/app@1 --cwd . --patch-server-url $MOCK --api-url $MOCK --org org --api-token fake
    bundle install && grep -c SOCKET_PATCHED vendor/bundle/ruby/*/gems/colorize-0.8.1/lib/colorize.rb
    Bundler (Ruby 3.3.6, Linux) vex before install bundle install installed bytes
    4.0.17 (×2) exit 0, not_affected (inline_mitigations_already_exist) for pkg:gem/colorize@0.8.1 exit 0, reads gems.locked unpatched (0 markers)
    2.6.9 exit 0, not_affected exit 0 unpatched
    2.4.22 n/a (no CHECKSUMS, so the leftover lock carries no registry pin)

    Control: the same project without the leftover Gemfile.lock makes vex refuse with "no hosted or vendored patch references were found", which is correct. After the install, vex in verify mode refuses (not_applied), so the false attestation only hits lock-only checkouts, such as CI that runs vex before bundle install. That matches the BUNDLER_LOCKS loop in vex/discover/gem.rs reading the lock Bundler ignores. The patch API and registry were mocked on loopback; the upstream was the real rubygems.org.


    Generated by Claude Code

  4. mikolalysenko commented on Oct 4, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Claiming this issue (shared root cause: the gem lock readers pick their lockfile by a hard-coded name instead of Bundler's loaded manifest/lock pair). Branch: agent/fix-gem-loaded-lock-readers. Claim-ID: 2026-10-04T04:20:29Z-681f48


    Generated by Claude Code

  5. mikolalysenko commented on Oct 4, 2026

    @mikolalysenko
    CollaboratorAuthor

    [agent] Draft PR: #750


    Generated by Claude Code

  6. added 2 commits that reference this issue on Oct 4, 2026
    9ea1929
    4834b26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:triagedarch-auditFiled by a scheduled architecture audit routine (see the architecture review discussion)bugSomething isn't workingpm:bundlerBundler (RubyGems)priority:p1

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions