Skip to content

Fix gem eject dry run missing group-block refusal (#1761) - #1762

Merged
Mikola Lysenko (mikolalysenko) merged 3 commits into
mainfrom
agent/fix-gem-eject-dry-run-gate
Oct 28, 2026
Merged

Mikola Lysenko (mikolalysenko) merged 3 commits into
mainfrom
agent/fix-gem-eject-dry-run-gate

Conversation

@mikolalysenko

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

Copy link
Copy Markdown
Collaborator

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

Fixes #1761

Root cause

socket-patch vendor (the hosted-pins eject, run_eject in
crates/socket-patch-cli/src/commands/vendor.rs) previews backend
refusals only through takeover_dry_refusal, which stages the restored
text and runs the vendored backend's dry run. The gem backend's dry run
does not evaluate the Gemfile declaration gate (a declaration inside a
group block, #775) or the manifest gate, so the preview reports
eject_planned / exit 0, while the wet run refuses with
gemfile_declaration_not_editable and rolls back (exit 1).

scan --mode vendored, get --mode vendored and the per-purl takeover
already consult gem_takeover_refusal before the restore; the eject
path never did.

Fix

The gem vendored backend (vendor_gem in
crates/socket-patch-core/src/vendor/gem.rs) now runs its pure
Gemfile/lock edit planner (gem_edits) in dry runs too, for a gem not
already wired, and returns the same refusal the wet run would. The
eject's existing preview (takeover_dry_refusal, which dispatches the
backend's dry run over the restored text) therefore sees the refusal,
and so does a plain vendor --dry-run.

A first commit special-cased the eject with gem_takeover_refusals_for;
/code-review high pointed out that it checked each gem against its own
one-pin restore and could hide earlier backend refusals, so the second
commit replaces it with the backend fix.

The wet eject is now refused up front too (eject_refused, nothing
written) instead of restoring, failing and rolling back.

Tests

  • vendor::gem::tests::test_dry_run_previews_declaration_refusal
    (unit, default cargo test): dry and wet both refuse a group-block
    declaration. Fails without the fix.
  • e2e_redirect_gem_build:
    gem_hosted_group_block_pin_survives_a_refused_vendored_takeover
    now also ejects the CHECKSUMS lock with vendor --dry-run and
    vendor, and the new
    gem_hosted_group_block_pin_survives_a_refused_eject_of_a_converged_lock
    covers the lock converged by an unfrozen bundle install (the issue's
    other matrix rows). Both failed before the fix
    (status: success, eject_planned, exit 0) and pass with it on
    Bundler 4.0.18 / Ruby 3.3.6.

Review follow-up (97d9a96)

A review pointed out that the dry hosted→vendored takeover in
vendor_records_reusing also previews the backend over the files on
disk. For a gem, the Gemfile there still wraps the declaration in the
hosted source … do block, so the new dry-run edit check could refuse
it as indented. Gems now stop after the restored-text preview
(takeover_dry_refusal), as bun and sbt already do. No CLI path reached
that branch with a hosted gem in my tests (vendor ejects whenever a
hosted pin is in scope); the guard is defensive.
New e2e: gem_hosted_pin_previews_and_runs_a_clean_eject. A top-level
hosted gem previews eject_planned (exit 0, nothing written), and the
wet eject then wires the vendored copy.

Local evidence

  • cargo fmt --all -- --check, 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 --test vendor_eject_fresh_checkout -- --ignored: all pass (35 + 9 capstones), Bundler 4.0.18 / Ruby 3.3.6.
  • cargo test --workspace --all-features --no-fail-fast: everything passes except 12 tests that also need a non-root user or CI's pinned bun (write-failure tests that chmod a file read-only, which root ignores; e2e_bun_lockb on local bun 1.4.4). None touches gem code; CI runs them in its own environment.
  • in_process_vendor, in_process_vendor_bun_takeover, CLI vendor unit tests: pass.
  • No wrapper changes needed (npm/, pypi/, gem/ only dispatch to the binary).

Per-issue checklist

https://claude.ai/code/session_011f9aeS3X2GZTkmFRmgCiSv


Generated by Claude Code

Assisted-by: Claude Code:claude-opus-5-5
`socket-patch vendor --dry-run` on a hosted Bundler project whose
patched gem sits inside a `group ... do` block reported the eject as
planned (exit 0), while the real `vendor` refused the same gem with
`gemfile_declaration_not_editable` and rolled back (exit 1). CI gating
on the dry run was told an eject would succeed that never could.

The eject now asks the gem takeover gate (manifest and Gemfile
declaration checks) for each gem pin before planning, as `scan` and
`get --mode vendored` already do. Dry and wet runs both refuse the
eject whole with `eject_refused`, and the wet run no longer restores
and rolls back first.

Fixes #1761

Assisted-by: Claude Code:claude-opus-5-5
Move the #1761 fix to the gem vendored backend: its dry run now
computes the Gemfile and lock edits the wet run would make and returns
the same refusal (for example a declaration inside a `group` block).

Every dry-run caller benefits, not just the eject: a plain
`vendor --dry-run` and the takeover previews over restored text now
report what the wet run would refuse. This replaces the eject-only
gate added in the previous commit, which checked each gem pin against
its own one-pin restore and hid earlier backend refusals.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 28, 2026 04:11
@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 01c0895. Configure here.

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

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review at head 01c0895.

  • CI: 82/82 checks on 01c0895 passed or skipped; mergeable, no conflicts.
  • Bugbot reviewed 01c0895: no issues found. No unresolved review threads.
  • PR does not touch CHANGELOG.md.
  • Reviewer focus: crates/socket-patch-core/src/vendor/gem.rs — the dry-run path now runs the same Gemfile/lock group-block refusal checks as the real eject, so vendor eject --dry-run no longer reports eject_planned for a gem it would refuse.

Slack announcement not sent this run (Slack send unavailable to the burn-down agent); the next run will retry.


Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review at head 01c0895.

  • CI: 82/82 checks on 01c0895 passed or skipped; mergeable, no conflicts.
  • Bugbot reviewed 01c0895: no findings.
  • Reviewer focus: the backend fix in vendor/gem.rs that makes the vendored dry run apply the same Gemfile/lock declaration refusals as the wet run, so vendor --dry-run and the eject preview now refuse a group-block pin instead of reporting eject_planned.

Generated by Claude Code

@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

Final review brief

What it does. socket-patch vendor (the hosted-pins eject) previewed a gem declared inside a Gemfile group block as eject_planned (exit 0), while the real eject refused with gemfile_declaration_not_editable and rolled back. The gem backend's dry run now runs the same pure Gemfile/lock edit planner (gem_edits) the wet run uses, so dry and wet refuse alike, and the wet eject now refuses up front with nothing written.

Risk: low. An 8-line change in one dry-run branch. gem_edits is the same planner the wet path calls a few lines later, it only reads, and it is skipped for already-wired gems exactly as on the wet path. A dry run can now refuse only where the wet run would have refused too.

Look here

Verified

Changes I made: none.

Open questions: none.

Auto-merge is armed, so approving sends this straight to the merge queue.


Generated by Claude Code

@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as draft October 28, 2026 14:11
Merged via the queue into main with commit 95f4cae Oct 28, 2026
82 checks passed
@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-gem-eject-dry-run-gate branch October 28, 2026 14:12
@mikolalysenko

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

@cursor

cursor Bot commented Oct 28, 2026

Copy link
Copy Markdown

Bugbot is paused — your team has an unpaid invoice

Bugbot is paused because your team has an unpaid invoice.

Pay it from the billing page in the Cursor dashboard and Bugbot resumes automatically.

(requestId: 6ad00737-14f4-4a72-a4ac-27818feec8a1)

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

4 participants