GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Fix gem source-block bang lost after x = if … end (#1476) by mikolalysenko · Pull Request #1478 · SocketDev/socket-patch

Conversation

@mikolalysenko

@mikolalysenko Mikola Lysenko (mikolalysenko) commented Oct 13, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Collaborator

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

Fixes #1476

Summary

Hosted gem rollback / remove now keep the DEPENDENCIES ! (and the
declaration's indent) for a gem declared in the user's own
source "…" do block when an assignment-form keyword construct
(v = if … end, v = case … end, v = begin … end, x ||= unless …)
precedes it. Before this, the restored lock dropped the ! and every
frozen/deployment install on Bundler 4.0.19+ failed with exit 16, while the
unwind reported success.

Root cause

in_source_block (crates/socket-patch-core/src/patch/redirect/upstream/gem.rs)
decides DepEntry::Keep vs Unpin by counting block openers and ends
line by line. opens_keyword_block only counted if/case/begin/…
when the keyword was the first token of the line. In v = if … nothing was
pushed, but the matching end still popped, removing the user's source
frame. declaration_prefix used the same line-level heuristics, so the
restored gem also came back at column 0 after a blank line.

Fix

One block scanner (block_stack) now feeds both in_source_block and
declaration_prefix:

  • word-level: a keyword opens a construct wherever it starts an expression
    (line start, after =, (, ,, ;, a label require:, =>, a
    ternary, and/or/when/then/else, an operator, or a
    continued previous line); end closes one; one-line constructs net out;
  • modifiers (gem "x" if c, x.save! if y, return if y, a modifier on a
    \\-continued line), names (x.class, :if, if:, begin?,
    def begin) and endless defs (def x = 1, def ==(o) = …) open nothing;
    a loop's own do is not a second block;
  • strings, backticks, %w[]/%i()/%q{}/%x()/… and regexp literals
    (multi-line strings and %-literals included), comments, =begin/=end
    blocks and heredoc bodies are not code;
  • the restored declaration's indent comes from the enclosing block's opener
    line (+2); a blank line is still restored before a following declaration.

Tests (red → green)

Issue case Test Before fix After
#1476 v = if … end lock ! + Gemfile byte-identical unit source_block_declaration_keeps_its_bang_through_restore (new manifests: if, case+begin, unless/while, deeper end) FAILED (\n\ngem "rails" at column 0, ! dropped) ok
#1476 scanner shapes (if/unless/case/begin/while/until/for, labels, ternary, literals, heredocs, =begin) unit source_block_detection FAILED ok
layout after an inner end unit manifest_blocks_restore_in_place — ok
#1476 e2e on real Bundler (v = if … end + w = case … end), rollback + remove, frozen install of the restored pair e2e_redirect_gem_build::gem_hosted_unwind_keeps_a_source_block_bang (new ScanVexSourceBlockAssignment driver) issue repro: exit 16 on 4.0.22 ok on Bundler 4.0.22 and 4.0.18

Commands run locally (Ruby 3.3.6):

  • cargo fmt --all -- --check ✅
  • cargo clippy --workspace --all-features -- -D warnings ✅
  • cargo test -p socket-patch-core --all-features --lib redirect::upstream::gem: 17 passed; with the fix reverted, 2 failed
  • BUNDLER_VERSION={4.0.22,4.0.18} SOCKET_PATCH_BUNDLER_E2E_REQUIRED=1 cargo test -p socket-patch-cli --all-features --test e2e_redirect_gem_build --test e2e_vendor_gem_build -- --ignored: 29 + 8 passed on each
  • cargo test --workspace --all-features --no-fail-fast: 14337 passed, 13 failed. All 13 are environmental and outside gem code: 12 use chmod to force write failures, which root ignores (the sandbox runs as uid 0), and e2e_bun_lockb::binary_shared_bundled_record_hosted_pin_is_managed needs the real patch API (tunnel error). CI runs these unprivileged.

No wrapper (npm/, pypi/, gem/) changes: this is core Rust logic only.

Review notes (/code-review high, two passes)

All confirmed findings are fixed in c9f3945 and 9f2c94f. Not changed:

  • vex/discover/gem.rs parse_source_block_decls keeps its own line
    grammar. It reads only Socket-written patch-registry blocks (which hold
    plain gem lines) and fails closed: an early close only drops
    attribution and never adds a ! or an attestation. Sharing the lexer
    there would be a separate refactor.
  • block_stack runs twice per restored direct gem (in_source_block and
    declaration_prefix) over a small Gemfile prefix. Threading one stack
    through restore_one → restore_manifest costs more code than it saves.
  • ruby_words looks like gem_line_tail_blocks_edit's classifier in
    redirect/mod.rs, but the two answer different questions (modifier vs
    argument position on a declaration line), so they stay separate.
  • Remaining text-lexing limits, same as before this PR: #{} interpolation
    that nests quotes, and percent literals after a bare method call
    (puts %w[…]).

🤖 Generated with Claude Code

https://claude.ai/code/session_01HuKqLAEGGwH73DucozCrY6


Generated by Claude Code

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@claude
Start fix for #1476
058f618
Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko Mikola Lysenko (mikolalysenko) mentioned this pull request Oct 13, 2026
Hosted gem rollback / remove still strip the DEPENDENCIES ! when the user's source … do block holds an assignment-form v = if/case/begin … end, so frozen installs fail on Bundler 4.0.19+ #1476
Closed
Claude (claude) added 3 commits October 13, 2026 10:40
@claude
Test gem unwind after x = if … end in source block
c3d338b
A gem declared in the user's own source "…" do block loses its
DEPENDENCIES `!` on hosted rollback/remove when an assignment-form
keyword construct (v = if/case/begin … end) precedes it, so frozen
installs fail on Bundler 4.0.19+ (#1476). Add unit cases for the
block scanner and the restore round trip, and an e2e Bundler case.

Assisted-by: Claude Code:claude-opus-5-5
@claude
Keep gem source-block bang after x = if … end
807e52d
The hosted gem unwind only counted if/case/begin/… as opening a block
when the keyword started its line. In `v = if …` nothing was opened,
but the matching `end` still closed the user's source block, so the
restored lock dropped its `!` pin and Bundler 4.0.19+ frozen installs
failed after rollback or remove. The restored declaration also came
back at column 0 after a blank line.

Count keywords word by word wherever they start an expression (after
=, (, ',', ;, …), treat `end` as closing one, net out one-line
constructs, skip modifiers, names like x.class or if:, and endless
defs. Derive the restored declaration's indent from the same block
stack.

Fixes #1476

Assisted-by: Claude Code:claude-opus-5-5
@claude
Indent restored gem from its enclosing block
b6dfe85
After an `end` indented deeper than the block's body (a case/when
laid out under its assignment), the restored declaration copied that
`end`'s indent, so the Gemfile did not come back byte for byte. Take
the indent of the line that opened the enclosing block + 2 instead.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko Mikola Lysenko (mikolalysenko) mentioned this pull request Oct 13, 2026
Swarm health manager log #1382
Open
Claude (claude) added 2 commits October 13, 2026 11:08
@claude
Skip literals, comments, heredocs in gem scan
c9f3945
Review follow-ups for the source-block scanner (#1476):
- a keyword after a label (require: if), =>, a ternary, and/or/not or
  an operator starts an expression, so its end is counted;
- %w[]/%i[] arrays, regexps, backticks, =begin/=end comments and
  heredoc bodies are no longer read as code;
- a ; ends a loop's statement, so a later do opens its own block;
- def begin / x.then if treat the keyword-like word as a name;
- the restored declaration keeps a blank line before a following
  declaration after an inner end, as before;
- the stack is computed once per restore, with opener line offsets.

Assisted-by: Claude Code:claude-opus-5-5
@claude
Carry literals across lines in gem block scan
9f2c94f
Second review pass on the source-block scanner (#1476):
- mask strings and %-literals that span lines, and find the comment
  with the same literal rules (a ' in a regexp, # in %q());
- add %s() / %x() literals;
- class <<self and list <<name are not heredocs;
- a modifier on a backslash-continued line stays a modifier;
- endless defs with operator names or nested parens;
- a multi-line block opener indents from its first line.

Assisted-by: Claude Code:claude-opus-5-5
@mikolalysenko
Mikola Lysenko (mikolalysenko) marked this pull request as ready for review October 13, 2026 11:45
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

BugBot review


Generated by Claude Code

cursor[bot] reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

cursor[bot]
cursor Bot reviewed Oct 13, 2026
View reviewed changes

@cursor cursor Bot left a comment

Copy link
Copy Markdown

There was a problem hiding this comment.

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 9f2c94f. Configure here.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

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

Mikola Lysenko (mikolalysenko) commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Burn-down agent: labeled Ready for review at head 9f2c94f.

  • CI: 35/35 green (+50 skipped) on 9f2c94f; no main-wide failures.
  • Bugbot: reviewed 9f2c94f, no findings (a later re-request was cancelled, not failed).
  • Mergeable, no conflicts; does not touch CHANGELOG.md.
  • Reviewer focus: crates/socket-patch-core/src/patch/redirect/upstream/gem.rs keyword/literal scanning for assignment-form x = if … end inside source blocks.

Generated by Claude Code

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@mikolalysenko
Mikola Lysenko (mikolalysenko) enabled auto-merge October 13, 2026 13:15
@mikolalysenko

Mikola Lysenko (mikolalysenko) commented Oct 13, 2026

Copy link
Copy Markdown
Collaborator Author

Final review brief (merge steward)

What it does: Hosted gem rollback/remove decides whether a restored gem sits inside the user's source "…" do block (and so keeps its ! in the lock's DEPENDENCIES) by counting block openers and ends. Before this PR, assignment-form constructs like v = if … end didn't count as openers but their end still closed the source block, so the ! was dropped and frozen Bundler 4.0.19+ installs failed with exit 16. A single word-level Ruby block scanner (block_stack) now feeds both in_source_block and the restored line's indent.

Risk: medium. The change is only in the gem unwind path, but it replaces a small line-level heuristic with roughly 450 lines of hand-written Ruby lexing (literals, heredocs, =begin, endless defs, modifiers vs. expression-position keywords). Unusual Gemfile syntax could still be misread, as it could before. When that happens, only the !/indent of the restored line is affected.

Look here:

  • block_stack gem.rs:565:`` the core scanner (do/keyword push, `end` pop, `;` statements, carried continuation lines)
  • starts_expression gem.rs:709:``` decides between a modifier (gem "x" if c) and an opener (`v = if c`)
  • masked_ruby_code gem.rs:812 and heredoc_openers gem.rs:940:````` literal, comment and heredoc masking
  • declaration_prefix gem.rs:512:`````` restored-line indent now comes from the enclosing block's opener. Top-level layout is unchanged.
  • e2e gem_hosted_unwind_keeps_a_source_block_bang:``````` real-Bundler rollback/remove plus a frozen install with the new ScanVexSourceBlockAssignment Gemfile

Verified:

  • Read the full diff (2 files, +637/−64): scope is limited to gem.rs and its e2e test. No CHANGELOG, wrapper or debug leftovers. CRLF is handled (split_inclusive('\n') + \r trim).
  • Checked that the top-level declaration_prefix output (blank line, next declaration's indent) matches the old behavior, so only in-block cases change. Those now get better indents, including multi-line group openers and if blocks.
  • Locally on 9f2c94f: cargo test -p socket-patch-core --all-features --lib redirect::upstream::gem gave 17/17 pass. CI's cargo clippy -p socket-patch-core --all-features -D warnings is clean, and so is cargo fmt --check. The --all-targets lints are pre-existing in other files and none are in gem.rs.
  • CI: 35/35 green, 50 path-skipped. ci-ok and clippy are green. Bugbot reviewed 9f2c94f with no findings. No unresolved threads.

Changes I made: none.

Open questions: none blocking. Known lexing limits are listed in the PR description: nested-quote #{} interpolation and puts %w[…] after a bare call. Both are the same as before this PR.

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


Generated by Claude Code

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Tanmay182003
Tanmay Singla (Tanmay182003) approved these changes Oct 13, 2026
View reviewed changes
@mikolalysenko
Mikola Lysenko (mikolalysenko) added this pull request to the merge queue Oct 13, 2026
Merged via the queue into main with commit 48ce1f4 Oct 13, 2026
86 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@mikolalysenko
Mikola Lysenko (mikolalysenko) deleted the agent/fix-gem-assignment-keyword-block branch October 13, 2026 14:15
Mikola Lysenko (mikolalysenko) added a commit that referenced this pull request Oct 13, 2026
@mikolalysenko @claude
chore(release): 5.0.1 notes for PRs merged since the cut
0b23ce5
Covers #1469, #1470, #1474, #1475, #1478, #1479, #1480 and #1482; #1484 is
CI-only. The notes now cover main through 285a5e8.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
github-merge-queue Bot pushed a commit that referenced this pull request Oct 13, 2026
@mikolalysenko @claude
chore(release): 5.0.1 (#1485)
2e0a6b9
* ci(release): publish npm from release.yml, not publish-npm.yml

Every socket-patch npm package's trusted publisher is registered against
release.yml, and npm allows only one publisher per package. The 5.0.0
release dispatched publish-npm.yml, whose OIDC token named that file, so
npm rejected it with E401 before staging anything.

Move the npm leg back inline as release.yml's npm-publish job (same steps
as publish-npm.yml) and delete publish-npm.yml, so the existing
registration keeps working. Add an `npm-retry-version` input that skips
the version gate, build, tag, GitHub release and crates.io jobs and
re-stages npm from an existing release's assets; this retries 5.0.0 now
and replaces the manual Publish npm dispatch. crates.io stays dispatched
through publish-cargo.yml, since crates.io allows several publishers per
crate.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* chore(release): fold 22 more PRs into the 5.0.0 notes

Cover the PRs merged to main after #1432's base (1779317..4cb3643):
Cargo source replacement and registry = "crates-io" (#338/#455, #386),
pnpm lockfile=false (#1074), berry removed descriptors (#1435), yarnrc
${VAR} reads (#1100), CRLF .npmrc append (#1196), Composer vendor-dir
cascade and global home (#439/#686, #586), Go +incompatible vex (#484),
gem moved BUNDLE_GEMFILE and commented Gemfile revert (#952, #988),
Poetry source = "pypi" (#1282) and Hatch path characters (#547).

No entries for #1411 (macOS fix to code #1393 added in this release;
the #1393 entry already describes the intended behavior), #1426 (perf
regression introduced and fixed within 5.0.0) or the CI/test/bench PRs.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* chore(release): cover #1419, #1434, #1440, #1442, #1444 in the 5.0.0 notes

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* chore(release): 5.0.1

Cut [Unreleased] into [5.0.1] — 2026-10-13 and stamp 5.0.1 with
scripts/version-sync.sh. The 5.0.1 notes cover main through 501c6d9
(#1449), i.e. every PR after the v5.0.0 tag (6ad6add): the npm
publish fix (5.0.0 never reached npm or crates.io, 5.0.1 is the first
v5 there), #1465, #759, #1460, #1462, #1445, #869, #1447, #1450,
#1467, and #1449 under Maintenance. CI/test-only PRs (#1448, #1452,
#1454, #1457, #1459) have no entries.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* chore(release): 5.0.1 notes for PRs merged since the cut

Covers #1469, #1470, #1474, #1475, #1478, #1479, #1480 and #1482; #1484 is
CI-only. The notes now cover main through 285a5e8.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
This was referenced Oct 13, 2026
CI performance dashboard #1182
Open
Hosted gem rollback / remove strip the DEPENDENCIES ! when the user's source block opener spans lines or uses braces, so frozen installs fail on Bundler 4.0.19+ #1509
Closed
Hosted gem rollback / remove strip the DEPENDENCIES ! when the user's source … do block opens after a ; on the same line, so frozen installs fail on Bundler 4.0.19+ #1531
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@cursor cursor[bot] cursor[bot] left review comments
@Tanmay182003 Tanmay Singla (Tanmay182003) Tanmay182003 approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

Ready for review Agent-verified: mergeable, CI green, Bugbot clean — awaiting human review

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Hosted gem rollback / remove still strip the DEPENDENCIES ! when the user's source … do block holds an assignment-form v = if/case/begin … end, so frozen installs fail on Bundler 4.0.19+

Uh oh!

There was an error while loading. Please reload this page.

3 participants

@mikolalysenko @Tanmay182003 @claude
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.