Skip to content

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

Merged
Mikola Lysenko (mikolalysenko) merged 9 commits into
mainfrom
agent/fix-gem-loaded-lock-readers
Oct 5, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 9 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:
  • 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.
  • Ported from Fix vex alias tests broken by store-copy merge #851 (tests only, so main's red socket-patch-cli --lib doesn't block this PR): two commands::vex_consumed tests adjusted after Fix npm store copies missed by agent apply and vex (#601, #603) #605. It becomes a no-op once Fix vex alias tests broken by store-copy merge #851 lands.
  • No CHANGELOG entry: release notes are written at release time.

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, also after the merges of main. --all-targets reports only pre-existing hits on main, none in the touched files.
  • cargo test --workspace --all-features --no-fail-fast: all binaries pass except 12 permission-injection tests (chmod 0o555 / unremovable-file write failures in covgap_commands_vendor, copy_tree, vlt_heal, pypi_poetry, pypi_requirements, repair_invariants). Those can't fail as root (the sandbox runs as uid 0), none touches gem code, and 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 1eedea8: all check runs green. Three PDM native jobs failed one random live-API case each and passed on a single re-run (details in the PR comments).
  • 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 gem lock drives inventory, VEX attestation, and hosted scan—incorrect choice previously allowed false not_affected while bundle install used an unpatched lock; behavior is now security-sensitive but narrowly scoped to Bundler discovery rules with extensive tests.

Overview
Fixes #736 by aligning all Ruby gem lock readers with the single manifest/lock pair Bundler actually loads (gems.rb + gems.locked when present, else Gemfile + Gemfile.lock, honoring BUNDLE_GEMFILE / .bundle/config).

Adds shared helpers bundler_loaded_manifest_in and bundler_loaded_lock_in in ruby_crawler.rs (hosted engine now calls the manifest helper instead of duplicating logic). Lock inventory and gem_remotes read only that lock; VEX gem discovery attests refs from the loaded lock only, while wiring in the ignored twin still parses for UUID recognition but surfaces patched_ref_unattributable diagnostics instead of refs.

Regression coverage includes gems.rb in-memory hosted redirect, inventory/memory-view/BUNDLE_GEMFILE cases, and stale-twin repros. npm vex_consumed tests are adjusted post-#605 so alias expansion is still exercised when the name-keyed resolver already finds aliases. The polyglot discovery test drops a vendored gems.locked that Bundler would never read.

Reviewed by Cursor Bugbot for commit 1eedea8. Configure here.

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.

Stale Bugbot comment from a previous run.

@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

Release notes are written when a release is cut, from the merged PR
log and the code, so PRs no longer edit CHANGELOG.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@mikolalysenko Mikola Lysenko (mikolalysenko) removed the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 5, 2026
Resolve the keep_bundler_loaded_gem_files conflict to the shared
resolver, and pass the new global-config argument (None for a memory
view) that #577 added to manifest::classify.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ao6g9qAnawPfNxv11f3wM
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator Author

CI status after merging main (failures listed by head):

coverage / test-release / test (macos-latest) on a3322ad (socket-patch-cli --lib): commands::vex_consumed::tests::hosted_expands_alias_only_copies and hosted_reuses_expanded_npm_copies_and_merges_alias_variants fail on main itself since 4646693 (#605), a semantic merge conflict with #738. I reproduced it on bare origin/main. I ported #851's tests-only fix here as 1eedea8, together with a merge of the latest main. The port becomes a no-op once #851 lands. CLI lib tests now pass 840/840 locally, and these checks pass on 1eedea8.

coverage-docker (composer) on a3322ad: the image build's curl to getcomposer.org exited with code 7 (couldn't connect) before any test ran. It passes on 1eedea8.

PDM native jobs (2.9.3 on a3322ad; 2.8.2, 2.22.4 and 2.29.2 on 1eedea8): each job failed exactly one case, a different one each time. Each failing check depends on a live patch-API round trip, and the slow cases took 35–43s against a normal 13–26s. The single re-run of the three failed jobs on 1eedea8 passed, which confirms transient failures (most likely patch-API contention from the concurrent compatibility runs), not this PR.


Generated by Claude Code

main is red since 4646693 (#605): two commands::vex_consumed tests
assumed the name-keyed resolver never returns npm-aliased copies, which
#605 changed. Same tests-only change as #851; it no-ops once main
carries it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ao6g9qAnawPfNxv11f3wM
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

bugbot run


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 1eedea8. Configure here.

@mikolalysenko Mikola Lysenko (mikolalysenko) added the Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review label Oct 5, 2026
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: Ready for review at 1eedea8.

  • CI: 461/461 check runs green (455 success, 6 skipped), mergeable clean.
  • Bugbot: reviewed 1eedea8, no findings; 0 unresolved review threads.
  • Reviewer focus: bundler_loaded_lock_in in crawlers/ruby_crawler.rs is now the single source of which gem lock is read; the ignored twin lock only produces patched_ref_unattributable diagnostics in VEX.
  • Note: the description mentions a CHANGELOG entry, but the PR does not touch CHANGELOG.md (correct per AGENTS.md; release notes are written at release time).

Generated by Claude Code

@mikolalysenko
Mikola Lysenko (mikolalysenko) merged commit 7ea685b into main Oct 5, 2026
503 of 506 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-gem-loaded-lock-readers branch October 5, 2026 17:26
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

3 participants