Skip to content

Fix gem lock readers ignoring gems.locked (#736) - #750

Open
Mikola Lysenko (mikolalysenko) wants to merge 5 commits into
mainfrom
agent/fix-gem-loaded-lock-readers
Open

Mikola Lysenko (mikolalysenko) wants to merge 5 commits into
mainfrom
agent/fix-gem-loaded-lock-readers

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 4, 2026 •

Copy link
Copy Markdown
Collaborator

LLM Description written by Claude Code:claude-opus-5-5

Fixes #736

Summary

socket-patch now reads the gem lock Bundler actually loads. Before this, a gems.rb project's gems.locked was invisible to the lock inventory (scan's lockfile supplement, the in-memory hosted engine, VEX ledger liveness), and ledger recovery read only Gemfile.lock. VEX discovery read both locks, so a leftover redirected Gemfile.lock beside gems.rb + gems.locked made vex attest not_affected while bundle install installed the unpatched gem from gems.locked. That repro is in the #736 comments, on real Bundler 2.6.9 and 4.0.17.

Root cause

Bundler loads one manifest/lock pair: gems.rb + gems.locked when the root holds a gems.rb, otherwise Gemfile + Gemfile.lock, unless BUNDLE_GEMFILE (env or .bundle/config) says otherwise. LoadedManifest::pair already models this, and the writers use it (#341, #390). The readers each chose their own lock: they hard-coded Gemfile.lock, or read both.

Change

  • New shared resolver in crawlers/ruby_crawler.rs:
    • bundler_loaded_manifest_in(view): on disk or a snapshot, the ambient env plus the app config; in a memory view, its own .bundle/config. This logic moved out of the hosted engine.
    • bundler_loaded_lock_in(view): the lock of that pair, or None for an unsupported BUNDLE_GEMFILE.
  • lock_inventory/gem.rs: inventory_gemfile_lock_raw_in and gem_remotes read only the loaded lock.
  • vex/discover/gem.rs: only the loaded lock yields refs. The twin Bundler ignores is still read through the guarded reader, so its Socket uuids stay recognized (rule 11) and a ledger claim can't attest them. Each ref the twin would have yielded becomes a patched_ref_unattributable diagnostic that names the lock Bundler loads. Bundler picks one pair deterministically, so rule 1 ("read every lock") applies only within that pair here, and the module docs now say so.
  • hosted/engine.rs: keep_bundler_loaded_gem_files reuses the shared resolver. Its behavior is unchanged.
  • Test change: polyglot_project_discovers_the_union_of_every_package_manager used to add a vendored gems.locked next to the bundler fixture's Gemfile.lock with no gems.rb. Bundler never reads that file, so it is now an ignored twin. The test keeps its vendored coverage through the Pipfile.lock wheel, and gem coverage through the hosted bundler fixture.
  • CHANGELOG entry under Fixed.

Per-issue checklist (#736 acceptance criteria)

  • inventory_project on gems.rb + gems.locked returns its gems: lock_inventory::tests::gem_inventory_reads_the_lock_bundler_loads
  • A stale Gemfile.lock beside gems.rb + gems.locked: inventory, every-lock inventory and VEX discovery read only gems.locked: same test, plus vex::discover::gem::tests::only_the_lock_bundler_loads_is_read and a_stale_redirected_gemfile_lock_beside_gems_rb_is_not_attested (the issue-comment repro)
  • .bundle/config BUNDLE_GEMFILE: Gemfile beside a gems.rb reads Gemfile.lock on disk and in memory: gem_inventory_reads_the_lock_bundler_loads, gem_inventory_memory_view_reads_the_lock_bundler_loads
  • In-memory hosted engine regression: a gems.rb project yields a gem candidate, with or without a stale twin: hosted_memory_engine::gems_rb_project_yields_its_gem_candidates
  • Ledger recovery remotes come from the loaded lock: gem_remotes_reads_the_lock_bundler_loads
  • Guard: without gems.rb, a stray gems.locked is ignored: gem_inventory_ignores_gems_locked_without_gems_rb

Test evidence

  • Red before the fix: the 5 new core tests failed on the base (gem_inventory_reads…, …memory_view…, gem_remotes_reads…, only_the_lock_bundler_loads_is_read, a_stale_redirected…). The engine test failed with left: 0, right: 1 (no candidate) when only lock_inventory/gem.rs was reverted.
  • Green after the fix: all of the above pass.
  • cargo clippy --workspace --all-features -- -D warnings: clean. --all-targets reports only pre-existing hits on main, none in the touched files.
  • cargo test --workspace --all-features --no-fail-fast: 214 test binaries ok. 12 tests fail locally, and every one is a permission-injection test (chmod 0o555 / unremovable-file write failures in covgap_commands_vendor, copy_tree, vlt_heal, pypi_poetry, pypi_requirements, repair_invariants). They can't fail as root (the sandbox runs as uid 0), and none touches gem code. CI runs as non-root.
  • Real-Bundler e2e (Ruby 3.3.6, Bundler 4.0.17): e2e_redirect_gem_build -- --ignored (11 passed), e2e_vendor_gem_build -- --ignored (6 passed), e2e_vex_lockfile gem (9 passed).
  • CI on b519ef6: all 461 check runs green (455 success, 6 skipped). An earlier e2e_vendor_jvm_build maven_reactor failure on the superseded head 71e4564 was a Maven Central HTTP 429 during fixture warm-up, and it passed on b519ef6.
  • cargo fmt --all -- --check isn't usable as a gate here: main itself isn't rustfmt-clean, and CI doesn't run it. The touched hunks are rustfmt-formatted.
  • No wrapper changes are needed (npm/, pypi/, gem/ only dispatch to the binary).

🤖 Generated with Claude Code

https://claude.ai/code/session_012Ao6g9qAnawPfNxv11f3wM


Note

Medium Risk
Changes which lock drives scan inventory and VEX attestation for Ruby projects with twin lockfiles; behavior becomes stricter (fewer false not_affected claims) but may surface new diagnostics where stale locks held old redirects.

Overview
Fixes incorrect gem lock selection so vex, scan’s lockfile supplement, and the in-memory hosted engine use the same lock Bundler loads (gems.locked for gems.rb projects, otherwise Gemfile.lock, honoring BUNDLE_GEMFILE / .bundle/config).

A shared resolver (bundler_loaded_manifest_in, bundler_loaded_lock_in) replaces ad hoc Gemfile.lock reads and the hosted engine’s duplicated manifest logic. Lock inventory and ledger GEM-remote recovery now inventory only that file. VEX discovery attributes patches only from the loaded lock; wiring in an ignored twin (e.g. stale redirected Gemfile.lock beside gems.rb + gems.locked) still recognizes Socket UUIDs but emits patched_ref_unattributable instead of attesting not_affected while Bundler installs unpatched gems.

Tests cover gems.rb layouts, config overrides, memory-hosted redirects, and the polyglot union test no longer treats an ignored gems.locked as a second vendored source.

Reviewed by Cursor Bugbot for commit b519ef6. Configure here.


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
A gems.rb project's gems.locked was invisible to the lock inventory,
ledger recovery read only Gemfile.lock, and VEX discovery read both
locks. A leftover redirected Gemfile.lock beside gems.rb + gems.locked
therefore made vex attest not_affected while bundle install installed
the unpatched gem from gems.locked.

Add one resolver for the lock Bundler loads (honouring BUNDLE_GEMFILE
and the app config) and route the inventory, gem_remotes, VEX
discovery and the hosted engine through it. VEX still reads the
ignored twin, but any Socket wiring there is diagnosed as
unattributable instead of attested.

Fixes #736

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) force-pushed the agent/fix-gem-loaded-lock-readers branch from 9ea1929 to 4834b26 Compare October 4, 2026 04:41
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 4, 2026 05:10
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

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

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Ready for review — burn-down agent.

  • Head: b519ef6fa6735b32736dd9bb3e654dd7fa1879a2
  • CI: 455/455 completed checks green (6 skipped by matrix rules, none failing)
  • Bugbot: reviewed b519ef6, no findings; no unresolved review threads
  • Mergeable against main; it only needs human approval.

Reviewers should focus on the twin-lock handling in vex/discover/gem.rs. A leftover Gemfile.lock next to gems.rb + gems.locked is now reported as patched_ref_unattributable and no longer attested. That changes how rule 1 ("read every lock") applies.

Slack announcement: not sent. This session's Slack connector has no send tool, so the next run will retry.


Generated by Claude Code

This branch has not been deployed

No deployments
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

Development

Successfully merging this pull request may close these issues.

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

2 participants