Hacktoberfest 2026:维护者为十月标记出来的 issue,仍然开放、适合新手。 浏览 Hacktoberfest issue

Hosted gem `rollback` / `remove` strips the `DEPENDENCIES` `!` of a gem the user declared inside a `source "https://rubygems.org" do` block, so every frozen install fails after the unwind

未关闭 适合新手
#1,056 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

维护者通常 1 天内回复

还没有人认领这个 Issue。

评估

难度
2/5
预计耗时
1-3 小时
新手友好度
80/100
Issue 类型
缺陷
描述清晰度
描述清楚
活跃度
活跃
技术栈
ruby, rust
领域
cli

调研方向

从 crates/socket-patch-core/src/patch/redirect/upstream/gem.rs 第354行附近开始查看。strip_suffix('!') 调用需要加保护条件:只有当恢复后的 Gemfile 不再在 source 块内(或通过 source:/git:/path: 选项)声明该 gem 时,才移除 !。检查 unwind 期间如何跟踪原始 Gemfile 的上下文。运行项目的测试套件,并按照 issue 中的复现步骤验证 rollback 后 frozen install 能够成功。

由索引模型根据 Issue 内容生成。

描述

agent:triaged bug bughunt pm:bundler priority:p1

[agent] Found by the scheduled Bundler (RubyGems) bug-hunt routine (ledger #316).

Summary

Bundler marks a dependency declared inside a source … do block with ! in the lock's DEPENDENCIES (colorize (= 0.8.1)!), even when that block's source is rubygems.org. The hosted scan of such a gem works: it nests the patch-registry block inside the user's block, and the fresh frozen install gets the patched bytes. The unwind (rollback, or remove <purl>) then restores the Gemfile byte for byte, so the declaration is back inside the user's source "https://rubygems.org" do block. But the unwind always drops the ! from the lock's DEPENDENCIES line. The restored pair no longer matches what Bundler writes, so BUNDLE_FROZEN=true bundle install fails with exit 16 ("Your lockfile needs to be updated, but it can't be because frozen mode is set"). rollback still reports success with hosted.reverted: [pkg:gem/[email protected]].

Impact

After undoing a patch, every CI or deployment (frozen) install of the project breaks until someone runs an unfrozen bundle install and commits the lock. The unwind is supposed to give back the original, installable pair.

Repro (Linux, Ruby 3.3.6, Bundler 4.0.22 or 2.6.9 with bundle lock --add-checksums)

Patch API and patch registry mocked on loopback (the run-13 mock from ledger #316); rubygems.org is the real upstream.

mkdir app && cd app
printf 'source "https://rubygems.org"\n\ngem "rake"\nsource "https://rubygems.org" do\n  gem "colorize", "0.8.1"\nend\n' > Gemfile
bundle lock && cp Gemfile.lock /tmp/orig.lock    # DEPENDENCIES: colorize (= 0.8.1)!
socket-patch scan --mode hosted --yes --api-url $MOCK --org org --api-token fake
BUNDLE_FROZEN=true BUNDLE_PATH=vb bundle install  # exit 0, patched bytes (OK)
socket-patch rollback --api-url $MOCK --org org --api-token fake --patch-server-url $MOCK
diff /tmp/orig.lock Gemfile.lock
#   <   colorize (= 0.8.1)!
#   ---
#   >   colorize (= 0.8.1)
git diff Gemfile                                  # empty: the Gemfile came back exactly
BUNDLE_FROZEN=true BUNDLE_PATH=vb2 bundle install # exit 16

socket-patch remove pkg:gem/[email protected] leaves the same diff, and a frozen install then fails with exit 16 too.

Expected vs actual

  • Expected: CLI_CONTRACT.md's "Hosted unwind coverage" gem row says the unwind undoes the source "<patch registry>" do … end block and "the pair comes back". The ! should only be dropped when the restored declaration no longer sits in a user source block (the case where hosted mode added it). Here the declaration's own block still pins the source, so the original ! must stay, and the lock should come back byte-identical to the pre-scan one.
  • Actual: the ! is always removed (the row says "the DEPENDENCIES pin loses its !" without exception), which leaves a pair that Bundler's frozen mode rejects.

Matrix

OS Ruby Bundler Shape Result
Linux 3.3.6 4.0.22 top-level source + source "https://rubygems.org" do gem … end reproduces (×2)
Linux 3.3.6 4.0.22 every gem inside one source "https://rubygems.org" do block reproduces
Linux 3.3.6 4.0.22 source … do + nested group :default do reproduces
Linux 3.3.6 2.6.9 (CHECKSUMS added) every gem inside one source block reproduces
Linux 3.3.6 4.0.22 remove <purl> instead of rollback reproduces
Linux 3.3.6 4.0.22 plain top-level declaration (control) pass (lock byte-restored)

macOS and Windows weren't probed. The lock surgery is OS-independent.

First bad version

Not bisected. This is current main d47eab3; the v5 upstream restore introduced the hosted unwind.

Suspect code

crates/socket-patch-core/src/patch/redirect/upstream/gem.rs:354: for a non-transitive gem, entry.strip_suffix('!') unconditionally rewrites the DEPENDENCIES line to the unpinned form. It doesn't check whether the restored Gemfile still declares the gem inside a source … do block (or with a source: / git: / path: option), and in that case Bundler keeps the !.

主要语言
Rust
星标
8
派生
0
平均合并
1 天 1 小时
30 天内合并 PR
257

环境准备

  • 没有 Dockerfile 或 Docker Compose 文件
  • 没有 Pull Request 模板
  • 阅读贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

SocketDev/socket-patch 的其他 Issue

查看 SocketDev/socket-patch 的全部 Issue

相似的 Issue

更多 Rust Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。