Repository navigation
Fix gem lockfile DSL call ignored (#1477) - #1479
Merged
Merged
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Bundler 4 lets the Gemfile pick its lock with `lockfile "custom.lock"`, and that call wins over BUNDLE_LOCKFILE from the environment and the config files. socket-patch only read those settings, so a hosted scan pinned the leftover Gemfile.lock, left custom.lock untouched, reported no refusal, and every frozen install afterwards failed (#1477). The shared gem resolver now reads the call from the manifest bundler loads (Gemfile or gems.rb, on disk and in memory) before the settings. A call naming the pair's own lock changes nothing; any other call, `lockfile false`, or a value that cannot be read statically is refused with redirect_gem_bundle_lockfile_unsupported, in hosted and vendored mode alike, with "remove the `lockfile` call" as the remedy. Tests: DSL reader and precedence unit tests, a vendored refusal test, a memory-engine test, and a bundler 4 e2e twin of the #749 test. Assisted-by: Claude Code:claude-opus-5-5
Review follow-ups for the Gemfile `lockfile` call (#1477): - `lockfile nil` drops the lock bundler would read, so it is refused like `lockfile false` instead of being read as no call. - A call later in a line (`group(:ci) { lockfile "x" }`, `a; lockfile "x"`, `cond && lockfile("x")`) is refused instead of missed. - Calls in files the Gemfile `eval_gemfile`s by a literal path are read too, since bundler evaluates them in the same builder. - A Gemfile in a legacy encoding or with a BOM is still scanned. - Heredoc bodies, the __END__ data section and local variables named `lockfile` (`||=`, `==`, `.foo`) no longer cause false refusals. - In the hosted engine the manifest is read through the project view, so the overlay of a just-rewritten Gemfile is what gets scanned and recorded; the in-memory absolute-path rule moved into the lexical comparison, so it covers both lockfile sources. Assisted-by: Claude Code:claude-opus-5-5
Clippy flagged the duplicated escape/interpolation test in the gem lockfile reader; one helper now decides whether a quoted Gemfile argument is a plain literal for both the `lockfile` and `eval_gemfile` readers. No behavior change. Assisted-by: Claude Code:claude-opus-5-5
The repo's BOM guard test requires every production BOM strip to go through formats::text (#905); the gem lockfile reader now does. Assisted-by: Claude Code:claude-opus-5-5
Mikola Lysenko (mikolalysenko)
marked this pull request as ready for review
October 13, 2026 12:53
Collaborator
Author
|
BugBot review Generated by Claude Code |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
Bugbot Autofix is ON, but none of the reported issues met your autofix severity threshold.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 582f898. Configure here.
Tanmay Singla (Tanmay182003)
approved these changes
Oct 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

LLM Description written by Claude Code:claude-opus-5-5
Fixes #1477
Summary
Bundler 4 lets the Gemfile name its own lock with
lockfile "custom.lock",and that call wins over
BUNDLE_LOCKFILEfrom the environment and bothconfig files. socket-patch only read the settings, so a hosted scan of such
a project pinned the leftover
Gemfile.lock, rewrote the Gemfile, leftcustom.lock(the lock bundler reads) untouched, reported no refusal, andevery frozen / deployment
bundle installafterwards failed. In-run VEXcould attest a lock that was never pinned.
The scan now refuses such a project with
redirect_gem_bundle_lockfile_unsupportedand writes nothing, in hosted andvendored mode alike, with "remove the
lockfilecall from the Gemfile" asthe remedy. A call naming the pair's own lock (
lockfile "Gemfile.lock")changes nothing and still wires the project.
Root cause
LoadedManifest::with_lockfile(formats/gem/manifest.rs) was the onlylockfile resolver and was fed only the env / app-config / global-config
values. Bundler 4's
Dsl#to_definition(lockfile = @lockfile unless @lockfile.nil?) lets the Gemfile call override all of them. Every gem lockreader (hosted rewriter, lock inventory, VEX, vendored backend) asks the one
resolver in
crawlers/ruby_crawler.rs, so the fix lives there.What changed
formats/gem/manifest.rs:gemfile_lockfilereads the call from Rubysource. Only
lockfile "<plain literal>"at the start of a statement is apath.
lockfile false/lockfile nil(bundler 4 then reads no lock),computed or interpolated values, modifiers, calls later in a line, and
calls that disagree all fail closed. Comments, strings, heredoc bodies,
=begin/=end,__END__and local variables namedlockfileareskipped.
with_gemfile_lockfilegives the call top precedence (newGemfileSetting::GemfileCall).eval_gemfile_pathslists literaleval_gemfiletargets.crawlers/ruby_crawler.rs: the settings probe now returns aBundlerSettingsvalue, andresolve_inreads the loaded manifest'scall through the
ProjectView(disk, snapshot overlay or memory). Itdecodes lossily (legacy encodings, BOM) and follows literal
eval_gemfiles inside the project, at most 32 files. The in-memoryabsolute-path rule moved into the lexical comparison, so it covers both
lockfile sources.
change.
Test evidence
Red→green: with
gemfile_lockfilestubbed to returnNone(the oldbehavior) the new tests fail, and with the fix they pass:
crawlers::ruby_crawler::tests::loaded_manifest_reads_the_gemfile_lockfile_callFAILED → okvendor::gem::tests::a_bundler4_gemfile_lockfile_call_is_refusedFAILED → okhosted_memory_engine::a_bundler4_gemfile_lockfile_call_is_refusedFAILED → oke2e_redirect_gem_build::gem_hosted_bundler4_gemfile_lockfile_call_redirects_nothing(real bundler 4.0.18, the issue's exact repro: frozen install after the
scan) FAILED at the refusal assertion → ok
Unit tests:
gemfile_lockfile_reads_the_dsl_call(each spelling, thefail-closed shapes, and the non-calls),
eval_gemfile_paths_reads_literal_paths,and
with_gemfile_lockfile_accepts_only_the_pairs_own_lock.Local runs:
cargo fmt --all -- --check: okcargo clippy --workspace --all-features -- -D warnings: okcargo test --workspace --all-features: everything passes except13 tests that fail only in this sandbox. Ten rely on
chmod 0555blocking writes, which root ignores (
covgap_commands_vendor×3,in_process_redirect×3,repair×2, plus the core lib'scopy_tree / vlt_heal / pypi_poetry / pypi_requirements tests). One
(
e2e_bun_lockb) needspatches-api.socket.dev, which the sandboxproxy blocks. None touch gem code.
e2e_redirect_gem_build+e2e_vendor_gem_build--ignoredon bundler4.0.18: 30 + 8 passed.
Per-issue checklist
lockfile "custom.lock"DSL call (Bundler 4), so it pins the unused Gemfile.lock, leaves the real lock untouched, and every frozen install fails #1477 hosted:gem_hosted_bundler4_gemfile_lockfile_call_redirects_nothing(e2e) andhosted_memory_engine::a_bundler4_gemfile_lockfile_call_is_refusedlockfile "custom.lock"DSL call (Bundler 4), so it pins the unused Gemfile.lock, leaves the real lock untouched, and every frozen install fails #1477 vendored (the issue suspected it was affected too, and it was):vendor::gem::tests::a_bundler4_gemfile_lockfile_call_is_refusedlockfile "custom.lock"DSL call (Bundler 4), so it pins the unused Gemfile.lock, leaves the real lock untouched, and every frozen install fails #1477lockfile false(related shape the issue mentions): refused, covered in the unit testsReview
I ran
/code-review highand fixed every confirmed finding in 7c215c9:lockfile nil, mid-line calls,eval_gemfiled calls, encoding/BOM,heredoc/
__END__/variable false positives, reading the manifest throughthe snapshot overlay, and the duplicated memory-view absolute-path check.
I didn't add a refusal for an unreadable Gemfile (permission denied): the
scan can't wire such a project anyway.
Follow-ups
eval_gemfilewith a computed path or a path outside the project is notfollowed. A
lockfilecall hidden there is still missed. That is rare,and refusing every computed
eval_gemfilewould block common setups.🤖 Generated with Claude Code
https://claude.ai/code/session_017y6beSAab1H495Uwtdu5dU
Generated by Claude Code
Note
Medium Risk
Changes shared Bundler manifest/lock resolution for all gem scan modes; mistakes in DSL parsing could refuse valid projects or miss rare
eval_gemfilepaths, though behavior is intentionally fail-closed.Overview
Fixes #1477: hosted and vendored gem flows now treat Bundler 4’s in-Gemfile
lockfile "…"call as the authoritative lock source, aboveBUNDLE_LOCKFILEand.bundle/config.Previously only env/config lock settings were resolved, so a project with e.g.
lockfile "custom.lock"could getGemfile.lockrewritten while bundler still readcustom.lock, with no refusal and broken frozen installs / misleading VEX.manifest.rsadds static parsing (gemfile_lockfile,eval_gemfile_paths,GemfileSetting::GemfileCall) andwith_gemfile_lockfileso non-default locks fail closed with guidance to remove the call; a call naming the pair’s own default lock is a no-op.ruby_crawler.rsrefactors manifest loading throughBundlerSettings::resolve_in, reading the DSL from the loaded manifest viaProjectView(including literaleval_gemfilechains, bounded depth).E2E, in-memory hosted, and vendored tests cover the DSL path alongside the existing custom-lockfile config case.
Reviewed by Cursor Bugbot for commit 582f898. Configure here.