Repository navigation
Fix gem takeover un-hosting a grouped gem (#775) - #776
Mikola Lysenko (mikolalysenko) wants to merge 10 commits into
Conversation
Assisted-by: Claude Code:claude-opus-5-5
Vendored mode cannot edit a gem declared inside a `group` block (or any other declaration its line grammar refuses), but the hosted -> vendored takeover only finds that out after it has restored the hosted pin. gem_vendor_target_preflight runs the backend's Gemfile declaration gate on the Gemfile and Gemfile.lock text the restore would leave, without writing anything, so the takeover can refuse first (#775). Assisted-by: Claude Code:claude-opus-5-5
`scan --mode vendored` and `get --mode vendored` over a hosted gem declared inside a `group ... do` block restored its upstream entry and only then hit `gemfile_declaration_not_editable`, so the gem ended up neither hosted nor vendored and the next frozen install loaded the unpatched gem. The dry run promised `would_vendor`. The takeover now asks the gem declaration preflight before the restore: the wet run fails `gemfile_declaration_not_editable` with the hosted Gemfile and Gemfile.lock untouched, and the dry-run preview reports the gem as `would_refuse` with the same code (#775). Fixes #775 Assisted-by: Claude Code:claude-opus-5-5
|
BugBot review Generated by Claude Code |
|
[agent] Ready for review at
Generated by Claude Code |
Conflicts: kept both sides' additions — CLI_CONTRACT takeover paragraph gets main's npm v1 lock sentence and the PR's gem preflight sentence, vendor_flow preview keeps both the gem takeover and npm lock would_refuse arms, and both new test sections/e2e drivers are kept. Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
|
[agent] Failing tests (
Why it isn't this PR's: the same two tests fail on Cause: the two merges conflict in meaning. #738 added these tests assuming Fix: I found no open PR for this. A minimal patch would update the two tests to #605's discovery: drop the I didn't re-run the job: the failure is deterministic on Generated by Claude Code |
Brings in the vex_consumed alias test fix (#849) that main's red test/test-release/coverage jobs were waiting on. Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
The dry-run preview already marked gems whose hosted->vendored takeover the gem gates refuse as would_refuse, but preflight_refused_purls only consulted the Bun, vlt and npm lock gates. A wet scan therefore kept those gems in the rollout set and downloaded them before the takeover refused. The planning pass now applies the same gem takeover gate. Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
The dry-run preview already reported a hosted gem whose vendored takeover the Gemfile declaration gate refuses (#775) as would_refuse, but the wet `scan` / `get --mode vendored` still planned it a rollout slot and fetched its patch view before the takeover refused it. The download phase's lock-text refusals now include the gem takeover refusal for hosted gems, so the view is never fetched, and scan's vendored planning pass leaves those gems out of the rollout. The e2e asserts that a wet scan fetches no view for the refused gem. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0145M3bGaAVYdPzNXXD75tqe
…on-preflight' into agent/fix-gem-takeover-declaration-preflight # Conflicts: # crates/socket-patch-cli/src/commands/scan/vendor_flow.rs
|
bugbot run Generated by Claude Code |
Resolved conflicts: - scan/vendor_flow.rs: kept main's new with_symlink_warnings helper and this branch's preflight_refused_purls doc (which also covers the gem takeover gate) and GlobalArgs signature. Updated main's new preview_vendor_json unit test to pass the branch's takeover_refusals argument (empty map). - tests/e2e_redirect_gem_build.rs: kept both new drivers (ScanVexGroupBlock from this branch, ScanVexCustomGitSource from main) in the enum, label match and redirect arm. Co-Authored-By: Claude <noreply@anthropic.com>
|
bugbot run Generated by Claude Code |
main has failed socket-patch-core's lib tests since Gradle support (#646) and the digest helpers (#865) both landed. The guard test production_digests_go_through_the_helpers flags three files #646 added that still hash inline: crawlers/gradle_cache.rs, patch/jvm_jar.rs and patch/sidecars/maven.rs. That breaks test, test-release and coverage on every open PR. Each inline sha1/sha256 call now goes through sha1_hex_of or sha256_hex_of, which compute the same lowercase hex. Behaviour is unchanged. Assisted-by: Claude Code:claude-opus-5-5 (cherry picked from commit 659ac2c)
|
[agent] Failing test: Why it isn't this PR's: the test fails the same way on Ported fix: I cherry-picked #878 ( Generated by Claude Code |
|
[agent] The red checks on What failed: the cancelled jobs carry the annotation "The job was not acquired by Runner of type hosted even after multiple attempts", so no test code ran. Done: I re-ran the cancelled jobs once in each finished run (npm, Gradle, pnpm, Go and Benchmarks). The Generated by Claude Code |
|
bugbot run Generated by Claude Code |
There was a problem hiding this comment.
✅ 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 ecd761e. Configure here.
|
[agent] Ready for review at Generated by Claude Code |
LLM Description written by Claude Code:claude-opus-5-5
Fixes #775
Summary
When a hosted gem is declared inside a
group … doblock,scan --mode vendored/get --mode vendoredno longer un-host it and then refuse it. The takeover now asks the gem backend's Gemfile declaration gate before it restores the hosted pin:failed gemfile_declaration_not_editable(exit 1 /partial_failure). The hostedGemfile/Gemfile.lockare byte-untouched, so the gem stays hosted-patched.would_refusewitherrorCode: gemfile_declaration_not_editable(exit 0, the same posture as the Bun/vltwould_refuserows). It no longer sayswould_vendor.socket-patch vendor(eject) already rolled back correctly and is unchanged.Root cause
The hosted→vendored takeover (
vendor_records_reusingincommands/vendor.rs) runsrestore_upstream, which writes the upstreamGemfile+Gemfile.lock, before the gem vendored backend runs. The only gem gate before the restore wasgem_manifest_refusal(a gems.rb twin orBUNDLE_GEMFILE). The declaration gate (plan_gemfile_edit/refuse_append_of_direct_dependency) only ran inside the backend, after the restore had been written. The dry-run preview is a ledger classification with no engine refusals, so it could not see the problem either.Changes
socket-patch-core/src/vendor/gem.rs: newgem_vendor_target_preflight, the gem twin ofyarn_berry_vendor_target_preflight. It runs a dry-runrestore_upstreamof the pin and evaluates the declaration gate on the restoredGemfile/Gemfile.locktext. That way socket-patch's own hostedsource … doblock is never mistaken for the user's declaration. It never writes.socket-patch-cli/src/commands/vendor.rs:gem_takeover_refusal(manifest gate, then the declaration preflight) is used by the takeover loop before the restore.gem_takeover_preview_refusalsdoes the same for the dry-run preview.scan/vendor_flow.rs,scan/mod.rs,get.rs:preview_vendor_jsontakes the resolved takeover refusals and renders them aswould_refuserows (JSON and the human[would-refuse]lines).get.rs(lock_text_refusals_for) andscan/vendor_flow.rs(preflight_refused_purls): the wet download phase and scan's rollout planning pass apply the same gem takeover refusal, so a refused gem is reported before its patch view is fetched and never takes a rollout slot (Bugbot finding onb8b7752).CLI_CONTRACT.md: new "Gem preflight before the takeover" paragraph under "Takeover reconciliation".No wrapper changes are needed:
npm/,pypi/andgem/only dispatch to the binary.Test evidence
get/scan --mode vendored)e2e_redirect_gem_build::gem_hosted_group_block_pin_survives_a_refused_vendored_takeover(real Bundler 4.0.17)vendor_takeover_reverted_redirectemitted, Gemfile/lock un-hosted. Green:failed gemfile_declaration_not_editable, files byte-identical.dry_run=truelegs forgetandscan"action": "would_vendor". Green:would_refuse+errorCode.vendor::gem::tests::takeover_preflight_*(3 tests: group block refused and nothing written, top-level hosted gem passes, a refused restore is left to the takeover)scan::vendor_flow::preview_tests::preview_marks_a_refused_takeover_would_refuseRed was shown by stubbing
gem_vendor_target_preflightto returnNone. Both the dry legs and the wet leg failed exactly as reported in #775.The e2e respells the mock upstream as
https://rubygems.org/in the hosted pair and serves it throughSOCKET_RUBYGEMS_URL. The takeover restore only re-derives a CHECKSUMS sha256 for a rubygems.org remote, which is the shape of the report.Commands run locally:
cargo clippy --workspace --all-features -- -D warnings: clean.cargo test -p socket-patch-cli --all-features --test e2e_redirect_gem_build --test e2e_vendor_gem_build -- --ignored: 12 + 6 passed.cargo test --workspace --all-features: all pass except 16 write-failure tests across 4 binaries. Those depend onchmodmaking files unwritable, which does not hold when running as root (uid 0) in this sandbox. They do not touch the changed code, and CI runs them non-root.mainis notcargo fmt-clean under the pinned toolchain, and CI has no fmt check. Only the hunks this PR changes are rustfmt-formatted, so the diff stays reviewable.Follow-ups (not in this PR)
groupblock and then refuses to vendor it (gemfile_declaration_not_editable), so the project silently goes back to unpatched #775's side note: a top-level declaration whose service has no prebuilt artifact (vendor_prebuilt_required) is still un-hosted before the failure. That is the same "restore written before the vendor step can fail" class as npm lockfileVersion 1: scan/get --mode vendored un-host a hosted patch and then refuse to vendor it, so the project silently goes back to unpatched (vendor eject rolls back correctly) #659 / npm vendored refuses a registry package with vendor_workspace_member whenever a local file: directory (or workspace member) has the same name@version, and the hosted→vendored takeover then un-hosts it, leaving it unpatched #688. A general fix would snapshot the takeover's restore and roll it back on any backend failure, asvendoreject already does. That is a cross-ecosystem change and is better as its own PR.🤖 Generated with Claude Code
https://claude.ai/code/session_0145M3bGaAVYdPzNXXD75tqe
Note
Medium Risk
Takeover ordering changes affect hosted gem projects switching to vendored mode; incorrect preflight could leave pins hosted or still un-host in edge cases, but the change aligns with existing Bun/npm preflight patterns and is heavily tested.
Overview
Hosted→vendored takeover for gems now runs the same pre-checks as the gem vendored backend before
restore_upstreamun-hosts the pin. That covers the manifest gate (gemfile_not_loaded) and the Gemfile declaration gate on the restored Gemfile text via a dry-run restore (gem_vendor_target_preflight), so gems declared insidegroupblocks stay hosted when vendored mode cannot edit them.Wet runs fail with
gemfile_declaration_not_editable(or other gem codes) withGemfile/Gemfile.lockunchanged. Dry-runscan/get --mode vendoredaddswould_refusepreview rows instead ofwould_vendor. The download phase and rollout planning apply the same refusals so refused gems are not fetched early.CLI_CONTRACT.mddocuments the gem preflight alongside Bun/npm takeover rules. E2E covers the group-block fixture (ScanVexGroupBlock).A small refactor routes Gradle/Maven JVM sha1/sha256 hashing through
crate::utils::digestingradle_cache,jvm_jar, andmavensidecars (behavior unchanged).Reviewed by Cursor Bugbot for commit ecd761e. Configure here.
Generated by Claude Code