Skip to content

fix(ruby): refresh standalone Cargo.lock and check the crates.io pin - #1456

Open
ya-luotao wants to merge 7 commits into
superradcompany:mainfrom
ya-luotao:ruby-standalone-lock
Open

fix(ruby): refresh standalone Cargo.lock and check the crates.io pin#1456
ya-luotao wants to merge 7 commits into
superradcompany:mainfrom
ya-luotao:ruby-standalone-lock

Conversation

@ya-luotao

@ya-luotao ya-luotao commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

TL;DR

The Ruby extension's committed Cargo.lock still resolved microsandbox 0.6.9 from crates.io while Cargo.toml pinned the current release exactly. Refresh it, make rake version_check reject that drift, resolve the unpatched pin in CI, re-lock it automatically after each crates.io publish, and keep the new assertion out of the one job that deliberately builds against the in-tree SDK.

Description

  • sdk/ruby/ext/microsandbox/Cargo.lock resolved registry microsandbox 0.6.9 (and every microsandbox-* sibling, and even its own microsandbox-ruby entry) against an exact pin many releases ahead. Nothing caught it: every Ruby CI job runs scripts/ci/ruby-sdk-checkout.sh prepare, which swaps in the workspace lock and patches microsandbox to sdk/rust, and rb_sys does not pass --locked, so gem install silently re-resolved. The gemspec ships ext/**/*, so published source gems carried a lying lock.
  • The lock cannot be refreshed at bump time: the new core crate is only published later by release.yml. So the refresh has to happen after publish.
  • Refresh the lock with cargo update -p microsandbox --precise <pin> (no [patch.crates-io] active). One --precise pulls all exact-pinned sibling crates along. The branch now sits at 0.6.15, refreshed after merging main.
  • rake version_check (a dependency of rake test) now collects the lock's [[package]] blocks named exactly microsandbox. No such block means the lock was not resolved from this manifest and aborts; every block with a source = "registry+..." line must name the pinned version, otherwise it aborts naming both values and the cargo update to run. The check is skipped only for a path-patched lock (blocks present but none registry-sourced, which is what CI's prepare step and rake cargo:patch_workspace produce) or a missing lock.
  • New check.yml job ruby-standalone-pin (gated like ruby-source-gem, no agentd needed): reads the pin from Cargo.toml, asks https://crates.io/api/v1/crates/microsandbox/<pin> with a User-Agent, and on 200 runs cargo fetch --locked in sdk/ruby/ext/microsandbox without the prepare step or any patch. On 404 (the version-bump PR before a release) it emits a notice and skips; any other status fails. Added to the summary aggregate job.
  • The job resolves and fetches rather than compiles: with the extension's default-features = false, features = ["net", "ssh"], microsandbox-filesystem's build script takes its non-prebuilt branch and requires the workspace's build/agentd, which does not exist in a registry checkout. That is a pre-existing problem with the source gem, independent of this lock, and is better handled in its own issue.
  • ruby-platform-gem now runs rake cargo:patch_workspace version_check gem:stage rather than rake version_check cargo:patch_workspace gem:stage. That job builds the platform binary from the in-tree Rust SDK and never consumes the committed lock, so asserting the crates.io pin against it was out of place — and fatal on a release-bump PR, where the manifest names a version crates.io does not carry yet and the standalone lock is still on the previous release (scripts/bump-version.sh refreshes only Cargo.lock and crates/agentd/Cargo.lock). The check would abort on all four platforms, prescribing a cargo update --precise that cannot resolve until after publication, and block the very PR that has to merge before the tag can be pushed. cargo:patch_workspace moves ext/microsandbox/Cargo.lock aside before anything else, so running it first makes version_check take its documented "a patched workspace may have no lock at all" skip for the crates.io half while still asserting the gem/manifest/pin lockstep that guards the patch swap, before gem:stage compiles. The unpatched crates.io assertion stays with ruby-standalone-pin, which resolves against the registry and already skips while the pin is unpublished.
  • release.yml refresh-lockfile now also waits for crates-publish, installs a stable Rust toolchain, runs cargo update -p microsandbox --precise "$VERSION" in sdk/ruby/ext/microsandbox, and includes the lock in the follow-up PR it already opens for sdk/node-ts/package-lock.json (title, commit message, body and add-paths updated). The two refreshes are independent: the job runs when either publish succeeded (!cancelled() && ... && (needs.npm-publish.result == 'success' || needs.crates-publish.result == 'success')), the setup-node, npm wait and npm install steps are guarded on needs.npm-publish.result == 'success', and the Rust toolchain and cargo update steps on needs.crates-publish.result == 'success'. Checkout and create-pull-request stay unguarded: the pinned action's README states that with no diff against the base branch no pull request is created and it exits silently. The PR body qualifies each lockfile paragraph on its publish having succeeded.
  • sdk/ruby/README.md documents the lock's lifecycle.
  • Decisions for maintainers:
    • The CI job skips on a 404 from crates.io. The alternative is to run it unconditionally with continue-on-error, which keeps a visible (yellow) signal on release PRs whose pin is not published yet at the cost of a noisier status.
    • refresh-lockfile waits for both publishes but requires neither on its own: it runs when at least one succeeded, and each registry's steps are guarded on that registry's publish. So a crates.io failure yields an npm-only PR, an npm failure yields a Ruby-only PR (previously the job required npm success, which would have left the Ruby lock stale), and only when both fail is the job skipped. Whichever lockfile is missing from the PR still has to be refreshed by hand (rake version_check prints the cargo command; the npm one is npm install --package-lock-only --ignore-scripts). The alternative, one job per registry, would open two follow-up PRs per release.
    • Two windows carry a pin the standalone lock cannot match, and they are handled differently. Before publication, on the release-bump PR, the lock is intentionally stale and no cargo update can fix it; the ruby-platform-gem reorder above and ruby-standalone-pin's 404 skip keep CI green, and a local unpatched rake version_check is the only thing that still aborts (run rake cargo:patch_workspace first, as CI now does). After publication, between crates-publish succeeding and the refresh PR being merged, main carries a published pin with a stale lock: there ruby-standalone-pin is legitimately red on any Ruby-touching push or PR and an unpatched local rake test aborts at version_check. That is accurate signal and it heals when the refresh PR merges (the refresh PR itself is green), but it means merging that PR promptly matters. If a quieter window is preferred, the fetch step could be made non-blocking on pull_request events while the tag's refresh PR is open.
    • Adjacent, not fixed here: the Ruby release path already carries two per-release manual edits that scripts/bump-version.sh does not make — sdk/ruby/lib/microsandbox/version.rb (the script touches no sdk/ruby path by name; the extension's Cargo.toml is only reached by its generic find ... sdk -name Cargo.toml sweep) and the hardcoded gem version in ruby-source-gem's smoke assertion, which chore(release): bump microsandbox to 0.6.15 #1449 had to fix in a follow-up commit. Folding both into the bump script would be a worthwhile separate change; this PR deliberately adds no third manual step, since the lock refresh is automated in release.yml.

Test Plan

  • cd sdk/ruby/ext/microsandbox && cargo update -p microsandbox --precise 0.6.15 with no patch active: all microsandbox-* entries and the lock's own microsandbox-ruby entry 0.6.14 -> 0.6.15, plus the windows-sys edge microsandbox-metrics 0.6.15 adds. (The original 0.6.9 -> 0.6.14 refresh moved msb_krun* 0.1.31 -> 0.1.32 the same way.)

  • cargo fetch --locked and cargo metadata --locked in sdk/ruby/ext/microsandbox: pass on the refreshed lock; fail on the previous lock with cannot update the lock file ... because --locked was passed.

  • cargo check --locked (default features): fails from a registry checkout because microsandbox-filesystem build.rs cannot find build/agentd (pre-existing, see above); passes in 10m45s with the agentd-aarch64 binary staged where that build script looks. cargo check --locked --features microsandbox_core/prebuilt passes in 14m41s.

  • cd sdk/ruby && rake version_check: passes on the refreshed lock; with the lock's microsandbox entry edited to an older release it aborts with:

    Ruby/standalone Cargo.lock resolves crates.io microsandbox 0.6.14 but Cargo.toml pins 0.6.15; run `cargo update -p microsandbox --precise 0.6.15` in ext/microsandbox (with no [patch.crates-io] active) and commit the lock
    

    With the exact microsandbox block renamed (all microsandbox-* siblings intact) it aborts with:

    Ruby/standalone Cargo.lock has no [[package]] entry named microsandbox, so it was not resolved from ext/microsandbox/Cargo.toml; run `cargo update -p microsandbox --precise 0.6.15` in ext/microsandbox (with no [patch.crates-io] active) and commit the lock
    
  • Release-bump rehearsal for the ruby-platform-gem reorder: scripts/bump-version.sh 0.6.16 plus the manual version.rb bump the real release PRs carry, leaving the standalone lock on 0.6.15. The old order (rake version_check) aborts naming a --precise 0.6.16 that cannot resolve, exit 1. The new order (rake cargo:patch_workspace version_check) exits 0, because patch_workspace has moved the lock aside. With version.rb desynced to 0.6.99 the reordered check still aborts with Ruby/Cargo package version mismatch, so the lockstep guard on the patch swap survives the reorder. rake cargo:unpatch_workspace restores the committed lock byte-identically.

  • rake cargo:patch_workspace && rake version_check && rake cargo:unpatch_workspace on an unbumped tree: version_check passes with the lock moved aside and with a path-only lock copied in; after unpatch the committed lock is byte-identical.

  • cd sdk/ruby && rake test with MSB_RUBY_INTEGRATION unset (agentd staged as above), run against the 0.6.14 pin before the merge from main: the extension compiles against the registry crates; 17 tests, 13 assertions, 0 failures, 0 errors, 7 omissions (integration tests omitted without a virtualization host). Not re-run at 0.6.15; version_check and cargo fetch --locked were.

  • The pin probe against crates.io: 0.6.15 -> 200, 0.6.99 -> 404, no User-Agent -> 403.

  • ruby -ryaml -e 'YAML.load_file(".github/workflows/check.yml"); YAML.load_file(".github/workflows/release.yml")' passes; actionlint is not installed locally.

  • No Rust source changed; cargo fmt --check and clippy are unaffected.

Generated with Claude Code

https://claude.ai/code/session_01RqjuPiGZ34s5CPfayN8JN4

Cargo.toml pins microsandbox =0.6.14 from crates.io, but the committed
lock still resolved 0.6.9 for microsandbox, every microsandbox-* sibling
crate and the extension package itself. Nothing built the lock as
shipped: CI swaps it for the workspace lock and patches microsandbox to
the in-tree SDK, and rb_sys does not pass --locked, so gem installs
silently re-resolved. Re-resolve it against the registry with

    cargo update -p microsandbox --precise 0.6.14

(no [patch.crates-io] active) so the lock the source gem ships agrees
with the pin and `--locked` builds succeed.
rake version_check compared the gem version, the extension package
version and the exact microsandbox pin, but never the committed
Cargo.lock, which is what the source gem ships and what --locked builds
resolve. Collect the lock's [[package]] blocks named exactly
`microsandbox`: abort when there is none (the lock was not resolved from
this manifest) and when a registry-sourced one names a different version
than the pin, telling the developer which cargo update to run. Blocks
that carry no `source` (the path-patched lock CI's prepare step and
cargo:patch_workspace produce) or a missing lock say nothing about the
pin and are skipped. Document the lock's lifecycle in the README.
Every Ruby job runs scripts/ci/ruby-sdk-checkout.sh prepare, which
replaces the extension's Cargo.lock with the workspace lock and patches
microsandbox to sdk/rust, so the committed lock and the registry pin
were never resolved as-is and the lock drifted to 0.6.9 unnoticed. Add
ruby-standalone-pin, which reads the pin from the extension manifest,
asks crates.io whether that version exists, and runs cargo fetch
--locked unpatched. A 404 (the version-bump PR ahead of a release)
skips the resolution with a notice; any other non-200 status fails.
Compiling is not attempted because the published core crates' build
scripts need the workspace's build/agentd without the prebuilt feature.
@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown

Reviews (2): Last reviewed commit: "ci(release): refresh the ruby extension ..." | Re-trigger Greptile

Comment thread .github/workflows/release.yml

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 93e0db7177

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/release.yml Outdated
The extension's Cargo.lock cannot be refreshed at bump time because the
new microsandbox crate is only published by this workflow. Extend the
refresh-lockfile follow-up job to also wait for crates-publish and run
cargo update -p microsandbox --precise "$VERSION" in
sdk/ruby/ext/microsandbox, adding the lock to the PR it already opens
for the npm lockfile. The two refreshes are independent: the job runs
when either publish succeeded, the npm steps are guarded on npm-publish
and the Rust steps on crates-publish, so one failed publish only drops
that lockfile from the PR instead of skipping the other refresh.
@ya-luotao
ya-luotao force-pushed the ruby-standalone-lock branch from 93e0db7 to 53eee67 Compare August 24, 2026 16:31

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 53eee67775

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sdk/ruby/Rakefile
@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown

Reviews (3): Last reviewed commit: "Merge branch 'main' into ruby-standalone..." | Re-trigger Greptile

ya-luotao and others added 2 commits August 28, 2026 10:49
Merging main moved the extension's exact pin to =0.6.15 while the
committed lock stayed at the 0.6.14 crate graph, which is exactly the
drift this branch teaches CI to reject: rake version_check aborts and
ruby-standalone-pin's `cargo fetch --locked` fails. Re-resolve with
cargo update -p microsandbox --precise 0.6.15 (no [patch.crates-io]
active), which pulls every exact-pinned sibling and the lock's own
microsandbox-ruby entry along; microsandbox-metrics 0.6.15 adds a
windows-sys edge that comes with it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1yXAfQTLSSVEMknbj49uM
ruby-platform-gem ran `rake version_check` before cargo:patch_workspace,
so the new standalone-lock assertion fired against the committed lock in
a job that never uses it. On a release-bump PR that is fatal: the bump
moves the Ruby manifest to a version crates.io does not carry yet and
leaves the standalone lock on the previous release (scripts/bump-version.sh
refreshes only Cargo.lock and crates/agentd/Cargo.lock), so the check
aborts on all four platforms and tells the maintainer to run a
`cargo update --precise` that cannot resolve until after publication.
That blocks the PR that has to merge before the tag can be pushed.

Run cargo:patch_workspace first. It moves ext/microsandbox/Cargo.lock
aside before anything else, so version_check takes the documented
"patched workspace may have no lock at all" skip for the crates.io half
and still asserts the gem/manifest/pin lockstep that guards the patch
swap, before gem:stage compiles. The unpatched crates.io assertion stays
where it belongs: ruby-standalone-pin resolves `cargo fetch --locked`
against the registry and already skips while the pin is unpublished.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1yXAfQTLSSVEMknbj49uM
@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown

Reviews (4): Last reviewed commit: "ci(ruby): patch the workspace before the..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant