ci(macos): consolidate libkrun on our own pinned source build - #694
Conversation
…facts Four new composite actions dissolve copy-pasted setup blocks: - setup-rust: free-disk + protoc + rust-cache preamble, previously inlined in ci.yml clippy AND duplicated inside core-tests - setup-libkrun-macos: the brew slp/krun tap install, previously copy-pasted across ci-macos.yml and release.yml (the release job's separate dylib-existence check is dropped like ci-macos's was in #678: a failed brew install already fails the step, and a missing libkrun still fails loudly at link/rewrite time) - setup-libkrun-linux: fetch-libkrun.sh + the LIBKRUN_PREFIX / LD_LIBRARY_PATH exports, previously duplicated between ci-linux-kvm.yml and release.yml's amd64 build - guest-artifacts: the kernel + rootfs cache pulls, previously repeated in ci-macos.yml, ci-linux-kvm.yml, and release.yml The two fetch composites retry 3x to absorb transient cache/network hiccups; commands, arguments, and env are otherwise unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The ~60-line inline install_name_tool rewrite + otool verification in build-release-macos-arm64 (grown across #664/#668/#670) moves to scripts/rewrite-macos-linkage.sh, command-for-command. A script lets CI exercise the production @rpath rewrite on a throwaway copy of the debug binary later, instead of the rewrite only ever running at release time. Signing stays the caller's job (Developer ID, last mutation before upload, per #680). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Install Minimal step guarded on steps.cache-minimal.outputs.cache-hit, but no step with id cache-minimal exists (leftover from a removed cache step), so the condition was always true. Remove it; the step runs unconditionally as it already did in practice. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three latent bugs surfaced by review once the previously-inline code became a parameterized, reusable script: - otool -L's header line is the binary's own path; a binary living under a path containing "libkrun" (e.g. a throwaway CI copy) would false-match and silently skip the real rewrite. Skip the header (NR>1). - install_name_tool -rpath errors when the bare @loader_path entry is already gone, so a second run on an already-rewritten binary hard-failed. Retarget only when the dev rpath is present. - set -o pipefail aborted the `current=` capture before the annotated "no libkrun load command" diagnostic could fire when otool itself fails. Guard the capture with || true; the -z check handles both. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Trigger the VM lanes when their composite actions change: ci-macos.yml and ci-linux-kvm.yml path filters now include the composites their setup runs through, so a composite-only edit can no longer merge unexercised and break the lanes post-merge. - Restore the libkrun.dylib existence check in setup-libkrun-macos: brew exits 0 for an installed-but-unlinked keg, so "a failed install fails the step" was unsound on a persistent shared runner; without the check a release build dies later with a raw `ld: library 'krun' not found` instead of a provisioning pointer. - Fail fast on deterministic errors: the mip CLI is validated/built once, outside the retry loops (guest-artifacts, setup-libkrun-linux), so a compile error or bad mip path fails in one attempt instead of being re-run into the job timeout. Only the network-bound materialize retries. - One retry implementation: scripts/ci/retry.sh replaces the three hand-copied divergent loops, and the previously-unretried gvproxy downloads (ci-linux-kvm + release) now use it too. - release.yml: drop the inline LIBKRUN_PREFIX re-hardcode (the composite publishes it); document why the job-wide LD_LIBRARY_PATH is safe (the prefix holds only libkrun/libkrunfw by construction); fix the linkage-step comment that implied a CI consumer exists. Not fixed: ci-netns.yml still inlines its rust preamble — its free-disk config differs (no remove_tool_cache) and the file is slated for deletion when the networking proofs are mothballed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Integrates #672 (per-VM /dev/vdb volume) with the composite extraction: - The two copies of main's new libkrun >= 1.19.0 (krun_add_disk3) symbol check in ci-macos.yml fold into the setup-libkrun-macos composite's verify step, so the release mac build gets it too. - The KVM lane's new krun_add_disk3 assert now reads LIBKRUN_PREFIX (published by the setup-libkrun-linux composite) — the KRUN_PREFIX env it referenced was retired with the composite extraction, so the auto-merged step would have probed an empty path. - main's crates/minimald/** path additions and the Session-E2E VM reap step (#588 fix for the merged e2e job) are kept as-is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Actions history for both VM lanes (last ~200 runs each, plus the first attempts of every manually re-run run) records not a single failure in the retried steps: the kernel/rootfs cache pulls, the libkrun prefix fetch, and the gvproxy download have never failed. The retries were insurance without receipts; remove them and the now-unused scripts/ci/retry.sh, returning the composites to plain extractions of the original steps (the mip prebuild and input pre-validation existed only to keep deterministic work out of the retry loops, so they go too). The transient failures the history DOES show are apt-get installs — the only main-branch KVM lane failure in the window and both of its manual reruns died in "Install build dependencies". Retry belongs there if anywhere, left for a separate change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The __krun-vmm-only reap from #672 is insufficient: main run 29032763009 — on the very commit that added it — still failed the cold `minimal ls` with `ssh connect: Disconnected` against a fully healthy guest (READY emitted, minimald listening on vsock:2222, no connection ever accepted): the #588 bridge wedge. The remaining leftover is the host gvproxy switch, which minvmd owns and Guest::drop never kills; the failing runs' proxy-publish WARN corroborates a lingering gvproxy. Reap any stray minvmd first (so nothing respawns), then the VMM and gvproxy. The proper fix — the session harness reaping its own process group — stays tracked under #588; this keeps the merged e2e job's steps isolated in the meantime. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI previously tested against the slp/krun Homebrew bottle (a full-feature build, version drifting with the tap) while the release shipped our trimmed source build (blk,net; no gpu, no init-blob) — which nothing executed before it reached users. Consolidate both on one build: - vendor/libkrun/libkrun.lock pins the containers/libkrun version AND its resolved commit (replaces the LIBKRUN_REF env pin; the build fetches the commit, so a moved tag cannot change what we build). - scripts/build-libkrun-macos.sh builds the trimmed dylib at the pin, asserts self-containment and the krun_add_disk3 (>= 1.19.0) API floor, sets the install name to @rpath/libkrun.1.dylib, ad-hoc signs, and stages libkrun.1.dylib + a libkrun.dylib linker symlink into a prefix. - setup-libkrun-macos builds on miss into a commit-keyed prefix ($HOME/.cache/minimal-ci/libkrun/<commit>) — actions/cache on GitHub-hosted runners, the persistent directory itself on the mini — verifies the staged dylib, and publishes LIBKRUN_PREFIX. minvmd's build.rs prefers LIBKRUN_PREFIX over /opt/homebrew and bakes it as an rpath, so a leftover brew libkrun on the runner is ignored; brew leaves the CI path entirely (and with it the installed-but-unlinked keg failure mode). - release build-libkrun-macos-arm64 ships from the same composite: the shipped dylib is by construction the one every macOS CI lane linked and booted. Developer ID signing flow unchanged (#680). With the @rpath install name, minvmd records @rpath/libkrun.1.dylib at link time — verified locally: the script builds a self-contained dylib (Hypervisor.framework/libiconv/libSystem only), minvmd links with the @rpath load command + @loader_path and prefix rpaths, and the binary loads. rewrite-macos-linkage.sh's -change becomes a natural no-op; only its @loader_path -> @loader_path/../lib retarget still mutates release binaries. Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughmacOS CI and release workflows now provision libkrun from the commit pinned in ChangesPinned libkrun provisioning
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant macOS Workflow
participant setup-libkrun-macos
participant build-libkrun-macos.sh
participant Release Signing
macOS Workflow->>setup-libkrun-macos: Provision pinned libkrun
setup-libkrun-macos->>build-libkrun-macos.sh: Build on cache miss
build-libkrun-macos.sh-->>setup-libkrun-macos: Stage verified dylib
setup-libkrun-macos-->>macOS Workflow: Export LIBKRUN_PREFIX
macOS Workflow->>Release Signing: Copy staged libkrun.1.dylib
Possibly related issues
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Comment |
Resolves against #692, which introduced the materialize composite (channel-released prebuilt mip; no source build) for the guest kernel/rootfs pulls — superseding this branch's guest-artifacts composite, which wrapped the same call sites around scripts/fetch-artifact.sh. Resolution: adopt materialize at every conflicted call site (ci-macos artifacts, ci-linux-kvm, release fetch-release-guest-artifacts, including main's removal of the now unneeded toolchain/protoc/cargo-cache steps in the release fetch job), delete the guest-artifacts composite, and point the VM lanes' composite path filters at .github/actions/materialize/** instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Integrates the concurrently pushed clean merge of main@71b364b4 (rcache refactor); this branch's own merge of main@cd1c95eb already contains that content. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Base moved to main's materialize composite (#692); union the mac lane's composite path filters (materialize replaces guest-artifacts, the vendor/libkrun/** pin entry stays). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#685 landed on main as a squash (790a422), so this branch's real history of the same content conflicts textually. Resolution keeps this branch's side everywhere both touched — it is the squash content plus this PR's libkrun-consolidation edits (own-build composite, provision step names, vendor/libkrun path entries). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
scripts/build-libkrun-macos.sh (1)
68-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd
--lockedto the cargo build for reproducibility.The script's own stated goal is a fully pinned, supply-chain-verified build (pinned commit, integrity check, no third-party bottle). Without
--locked, cargo can silently re-resolve and updateCargo.lockif any dependency spec in the trimmed feature set isn't already locked, undermining that exact-reproducibility guarantee.♻️ Proposed fix
-cargo build --release -p libkrun --no-default-features --features blk,net \ - --manifest-path "$WORK/Cargo.toml" +cargo build --release --locked -p libkrun --no-default-features --features blk,net \ + --manifest-path "$WORK/Cargo.toml"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/build-libkrun-macos.sh` around lines 68 - 69, Add the --locked flag to the cargo build command in the libkrun build script, preserving the existing package, feature, release, and manifest-path options.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/actions/setup-libkrun-macos/action.yml:
- Around line 19-50: Update the prefix generated by the “Resolve libkrun pin”
step to include the build script hash alongside the libkrun commit, ensuring
self-hosted runners use a distinct directory when scripts/build-libkrun-macos.sh
changes. Keep the cache key and PREFIX usage consistent with this revised prefix
so “Build libkrun from source (on miss)” cannot reuse an outdated dylib.
---
Nitpick comments:
In `@scripts/build-libkrun-macos.sh`:
- Around line 68-69: Add the --locked flag to the cargo build command in the
libkrun build script, preserving the existing package, feature, release, and
manifest-path options.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8e7ef217-7862-4de9-b3c2-c105dd78000f
⛔ Files ignored due to path filters (1)
vendor/libkrun/libkrun.lockis excluded by!**/*.lock
📒 Files selected for processing (4)
.github/actions/setup-libkrun-macos/action.yml.github/workflows/ci-macos.yml.github/workflows/release.ymlscripts/build-libkrun-macos.sh
- Key the on-disk libkrun prefix by BOTH build inputs (pinned commit + build-script hash), not commit alone: the persistent mini could otherwise keep serving a dylib built by an older build-libkrun-macos.sh after a script change. This mirrors the invalidation the actions/cache key already gave GitHub-hosted runners; stale sibling prefixes on the mini are inert. - cargo build --locked: build exactly upstream's committed Cargo.lock (verified present at the pin) so a silent dependency re-resolve cannot undermine the reproducible-build guarantee. Verified locally: the --locked build completes at the pin and stages into the new hash-suffixed prefix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
CodeRabbit findings addressed in 2c3ff8f: the self-hosted prefix is now keyed by pinned-commit and build-script hash (the mini can no longer reuse a dylib from an older script), and the libkrun build uses |
The e2e's combined `cargo build -p minvmd --bin minvmd -p minimal --bin minimal` unified minvmd's default `libkrun` feature into the CLI's default-features=false opt-out, so `minimal` linked libkrun — the exact footgun release.yml already documents and avoids with separate invocations. The brew era masked it: brew's libkrun carries an absolute /opt/homebrew install name, so the mislinked CLI still loaded. The own-build dylib's @rpath install name exposed it — the autospawn e2e died with dyld "Library not loaded: @rpath/libkrun.1.dylib / no LC_RPATH's found" from target/debug/ minimal, which bakes no rpaths. Split the build (mirroring release.yml) and add the release job's "minimal links only system libraries" assert to the e2e, so a unification regression fails at build time with a pointed message instead of a dyld error mid-test. Net effect of this PR's own-build switch: CI now catches a mislinked CLI that brew silently tolerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
.github/workflows/ci-macos.yml (2)
23-24: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winInclude the libkrun lockfile in both path filters.
vendor/libkrun.lockis not matched byvendor/libkrun/**, so changing only the pinned version or commit can skip macOS CI entirely. Add the lockfile explicitly.- - vendor/libkrun/** + - vendor/libkrun.lock + - vendor/libkrun/**Also applies to: 40-41
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/ci-macos.yml around lines 23 - 24, The macOS CI path filters omit vendor/libkrun.lock, so lockfile-only pin changes can skip CI. Update both path-filter sections in the workflow to explicitly include vendor/libkrun.lock alongside the existing vendor/libkrun/** entry.
259-276: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winClean up the per-run state directory.
mktemp -dcreates a fresh directory on every persistent-runner execution, but the EXIT cleanup only stopsminvmd. Remove$XDG_STATE_HOMEafter stopping the daemon to prevent accumulation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/ci-macos.yml around lines 259 - 276, Clean up the temporary XDG state directory in the macOS CI workflow after the daemon is stopped. Update the EXIT cleanup trap associated with the `XDG_STATE_HOME` setup to remove `$XDG_STATE_HOME` after invoking the existing `minvmd` shutdown logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci-macos.yml:
- Around line 196-207: Update the “Verify minimal links only system libraries”
step so `otool -L` runs separately and its failure immediately fails the
workflow; avoid applying `|| true` to the entire inspection pipeline. Then
filter the captured dependency output for non-system libraries, tolerating only
an empty match result, while preserving the existing diagnostic and exit
behavior.
---
Outside diff comments:
In @.github/workflows/ci-macos.yml:
- Around line 23-24: The macOS CI path filters omit vendor/libkrun.lock, so
lockfile-only pin changes can skip CI. Update both path-filter sections in the
workflow to explicitly include vendor/libkrun.lock alongside the existing
vendor/libkrun/** entry.
- Around line 259-276: Clean up the temporary XDG state directory in the macOS
CI workflow after the daemon is stopped. Update the EXIT cleanup trap associated
with the `XDG_STATE_HOME` setup to remove `$XDG_STATE_HOME` after invoking the
existing `minvmd` shutdown logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 479d21ae-604c-46d0-8ef1-a51791a26754
📒 Files selected for processing (1)
.github/workflows/ci-macos.yml
| - name: Verify minimal links only system libraries | ||
| # The CLI spawns minvmd as a subprocess and calls no libkrun; a | ||
| # krun load command means the feature unification above regressed. | ||
| # Catch it here with a clear message, not as a dyld error mid-e2e. | ||
| run: | | ||
| bad="$(otool -L target/debug/minimal | tail -n +2 | awk '{print $1}' \ | ||
| | grep -vE '^(/usr/lib/|/System/)' || true)" | ||
| if [ -n "$bad" ]; then | ||
| echo "::error::minimal links non-system libraries (feature unification with minvmd's libkrun?):" >&2 | ||
| printf '%s\n' "$bad" >&2 | ||
| exit 1 | ||
| fi |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Fail closed when dependency inspection fails.
Because || true covers the entire pipeline, an otool failure can be converted into an empty bad value and pass the check. Capture otool -L separately so only “no non-system matches” is tolerated.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci-macos.yml around lines 196 - 207, Update the “Verify
minimal links only system libraries” step so `otool -L` runs separately and its
failure immediately fails the workflow; avoid applying `|| true` to the entire
inspection pipeline. Then filter the captured dependency output for non-system
libraries, tolerating only an empty match result, while preserving the existing
diagnostic and exit behavior.
#694 landed on main as a squash (7777fa8), so this branch's real history of the same content conflicts textually. Both hunks resolve to this branch's side (squash content + this PR's unit-tier edits: krun_smoke in the no-run build, rust-cache on the unit job). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nified session e2e, scripts over YAML Four changes, one review story (the KVM lane reaches its final shape): - nextest archive replaces the hand-rolled testbins.json + run-testbin.sh: build-linux ships testbed/nextest-archive.tar.zst and the test job selects harnesses with filtersets under the new profile.vm (one VM at a time, hung boots hard-killed at 6 min, no retries - a same-invocation retry runs against the leaked vmm/gvproxy of the failed attempt, #588). - the unified session e2e (scripts/session-e2e.sh) joins the lane, covering Deployment Model 3 (native Linux + VM): the minimal CLI is built in build-linux (separate invocation - a combined build unifies libkrun into the CLI, the #694 regression class), ships in the testbed, and drives activate/exec/destroy from PATH, with the #588 reap (sudo: relay leftovers are root-owned) before it and cli-e2e-boot.log uploaded. - the daemon-lifecycle shell blob becomes scripts/lifecycle-e2e.sh: PATH-resolved minvmd (no cargo, macOS-reusable), temp workdir, and an EXIT-trap teardown so a failed assert cannot strand a daemon. - the krun_add_disk3 export assert moves into setup-libkrun-linux, mirroring the macOS composite and covering release.yml (which uses the composite but had no check); an early actionable failure - minvmd's compile-time link is the real backstop. Also per owner direction: workflow comments drop PR/issue and R-numbers (history belongs in commits, not YAML), and the DM labels are corrected against docs/specs/03-spec-networking (DM1 is the macOS model; this lane is DM3, session-e2e.sh header fixed accordingly). Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four changes, one review story (nextest archive, unified session e2e, scripts over YAML, composite verify): - nextest archive replaces the hand-rolled testbins.json + run-testbin.sh: build-linux ships testbed/nextest-archive.tar.zst and the test job selects harnesses with filtersets under the new profile.vm (one VM at a time, hung boots hard-killed at 6 min, no retries - a same-invocation retry runs against the leaked vmm/gvproxy of the failed attempt, #588). - the unified session e2e (scripts/session-e2e.sh) joins the lane, covering Deployment Model 3 (native Linux + VM): the minimal CLI is built in build-linux (separate invocation - a combined build unifies libkrun into the CLI, the #694 regression class), ships in the testbed, and drives activate/exec/destroy from PATH, with the #588 reap (sudo: relay leftovers are root-owned) before it and cli-e2e-boot.log uploaded. - the daemon-lifecycle shell blob becomes scripts/lifecycle-e2e.sh: PATH-resolved minvmd (no cargo, macOS-reusable), temp workdir, and an EXIT-trap teardown so a failed assert cannot strand a daemon. - the krun_add_disk3 export assert moves into setup-libkrun-linux, mirroring the macOS composite and covering release.yml (which uses the composite but had no check); an early actionable failure - minvmd's compile-time link is the real backstop. Also per owner direction: workflow comments drop PR/issue and R-numbers (history belongs in commits, not YAML), and the DM labels are corrected against docs/specs/03-spec-networking (DM1 is the macOS model; this lane is DM3, session-e2e.sh header fixed accordingly). Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ci: adopt .config/nextest.toml with a ci profile Add a nextest config with two profiles: profile.default for local runs (surface slow tests, never kill), and profile.ci for the core-tests composite (fail-fast off for full failure reports, hard-kill at 5 minutes via slow-timeout terminate-after, leak detection at 1s). No retries anywhere: the workspace suite has no recorded flakes, and a same-invocation retry of a VM boot test would run against the leaked __krun-vmm/gvproxy processes of the failed attempt (#588) - the lane reap steps plus terminate-after remain the mitigation. core-tests also gains --no-tests=fail: an empty selection is a broken filter or a dropped target, not a pass. Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linux-native): trigger the lane on nextest config changes A PR touching only .config/nextest.toml (e.g. a future profile edit) must re-run the tests that consume it; this PR's own run only triggered because it also edited the core-tests composite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linux-kvm): reshape the lane to its end state Four changes, one review story (nextest archive, unified session e2e, scripts over YAML, composite verify): - nextest archive replaces the hand-rolled testbins.json + run-testbin.sh: build-linux ships testbed/nextest-archive.tar.zst and the test job selects harnesses with filtersets under the new profile.vm (one VM at a time, hung boots hard-killed at 6 min, no retries - a same-invocation retry runs against the leaked vmm/gvproxy of the failed attempt, #588). - the unified session e2e (scripts/session-e2e.sh) joins the lane, covering Deployment Model 3 (native Linux + VM): the minimal CLI is built in build-linux (separate invocation - a combined build unifies libkrun into the CLI, the #694 regression class), ships in the testbed, and drives activate/exec/destroy from PATH, with the #588 reap (sudo: relay leftovers are root-owned) before it and cli-e2e-boot.log uploaded. - the daemon-lifecycle shell blob becomes scripts/lifecycle-e2e.sh: PATH-resolved minvmd (no cargo, macOS-reusable), temp workdir, and an EXIT-trap teardown so a failed assert cannot strand a daemon. - the krun_add_disk3 export assert moves into setup-libkrun-linux, mirroring the macOS composite and covering release.yml (which uses the composite but had no check); an early actionable failure - minvmd's compile-time link is the real backstop. Also per owner direction: workflow comments drop PR/issue and R-numbers (history belongs in commits, not YAML), and the DM labels are corrected against docs/specs/03-spec-networking (DM1 is the macOS model; this lane is DM3, session-e2e.sh header fixed accordingly). Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* ci: adopt .config/nextest.toml with a ci profile Add a nextest config with two profiles: profile.default for local runs (surface slow tests, never kill), and profile.ci for the core-tests composite (fail-fast off for full failure reports, hard-kill at 5 minutes via slow-timeout terminate-after, leak detection at 1s). No retries anywhere: the workspace suite has no recorded flakes, and a same-invocation retry of a VM boot test would run against the leaked __krun-vmm/gvproxy processes of the failed attempt (#588) - the lane reap steps plus terminate-after remain the mitigation. core-tests also gains --no-tests=fail: an empty selection is a broken filter or a dropped target, not a pass. Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linux-native): trigger the lane on nextest config changes A PR touching only .config/nextest.toml (e.g. a future profile edit) must re-run the tests that consume it; this PR's own run only triggered because it also edited the core-tests composite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linux-kvm): reshape the lane to its end state Four changes, one review story (nextest archive, unified session e2e, scripts over YAML, composite verify): - nextest archive replaces the hand-rolled testbins.json + run-testbin.sh: build-linux ships testbed/nextest-archive.tar.zst and the test job selects harnesses with filtersets under the new profile.vm (one VM at a time, hung boots hard-killed at 6 min, no retries - a same-invocation retry runs against the leaked vmm/gvproxy of the failed attempt, #588). - the unified session e2e (scripts/session-e2e.sh) joins the lane, covering Deployment Model 3 (native Linux + VM): the minimal CLI is built in build-linux (separate invocation - a combined build unifies libkrun into the CLI, the #694 regression class), ships in the testbed, and drives activate/exec/destroy from PATH, with the #588 reap (sudo: relay leftovers are root-owned) before it and cli-e2e-boot.log uploaded. - the daemon-lifecycle shell blob becomes scripts/lifecycle-e2e.sh: PATH-resolved minvmd (no cargo, macOS-reusable), temp workdir, and an EXIT-trap teardown so a failed assert cannot strand a daemon. - the krun_add_disk3 export assert moves into setup-libkrun-linux, mirroring the macOS composite and covering release.yml (which uses the composite but had no check); an early actionable failure - minvmd's compile-time link is the real backstop. Also per owner direction: workflow comments drop PR/issue and R-numbers (history belongs in commits, not YAML), and the DM labels are corrected against docs/specs/03-spec-networking (DM1 is the macOS model; this lane is DM3, session-e2e.sh header fixed accordingly). Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linux-kvm): reap harness leftovers before the daemon lifecycle step The lifecycle step spawns a fresh daemon just like the CLI session e2e, so it was equally exposed to leaked __krun-vmm/gvproxy children from a failed harness run. Move the reap to run immediately after the harness e2es, ahead of both fresh-daemon steps. Addresses CodeRabbit review on #702. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: cache discipline, slim CI debug info, and a --locked sweep Three cross-lane changes that must fork every cache exactly once: - rust-cache gains shared-key (one per job class that compiles the same artifacts: workspace/tests/clippy/native-e2e/mac-unit) and save-if restricted to main, via new setup-rust inputs forwarded from core-tests. PR branches restore but never write, so PR churn stops LRU-evicting the main caches every PR restores from (the 10 GB pool is shared repo-wide). - the two multi-GB raw actions/cache users (KVM build-linux, macOS artifacts stage2) split into cache/restore + main-only cache/save guarded on cache-hit. - CARGO_PROFILE_DEV_DEBUG=line-tables-only in CI (setup-rust env step; explicit job env on the mac and KVM jobs that bypass the composite): usable backtraces, smaller target trees and caches, faster links - without a named Cargo profile, which would move output out of target/debug and break codesign/testbed/justfile paths. - --locked on every workflow cargo invocation (build/test/clippy/ archive), so a stale Cargo.lock fails loudly instead of silently re-resolving; previously only core-tests' cargo fetch enforced it. Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* ci: adopt .config/nextest.toml with a ci profile Add a nextest config with two profiles: profile.default for local runs (surface slow tests, never kill), and profile.ci for the core-tests composite (fail-fast off for full failure reports, hard-kill at 5 minutes via slow-timeout terminate-after, leak detection at 1s). No retries anywhere: the workspace suite has no recorded flakes, and a same-invocation retry of a VM boot test would run against the leaked __krun-vmm/gvproxy processes of the failed attempt (#588) - the lane reap steps plus terminate-after remain the mitigation. core-tests also gains --no-tests=fail: an empty selection is a broken filter or a dropped target, not a pass. Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linux-native): trigger the lane on nextest config changes A PR touching only .config/nextest.toml (e.g. a future profile edit) must re-run the tests that consume it; this PR's own run only triggered because it also edited the core-tests composite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linux-kvm): reshape the lane to its end state Four changes, one review story (nextest archive, unified session e2e, scripts over YAML, composite verify): - nextest archive replaces the hand-rolled testbins.json + run-testbin.sh: build-linux ships testbed/nextest-archive.tar.zst and the test job selects harnesses with filtersets under the new profile.vm (one VM at a time, hung boots hard-killed at 6 min, no retries - a same-invocation retry runs against the leaked vmm/gvproxy of the failed attempt, #588). - the unified session e2e (scripts/session-e2e.sh) joins the lane, covering Deployment Model 3 (native Linux + VM): the minimal CLI is built in build-linux (separate invocation - a combined build unifies libkrun into the CLI, the #694 regression class), ships in the testbed, and drives activate/exec/destroy from PATH, with the #588 reap (sudo: relay leftovers are root-owned) before it and cli-e2e-boot.log uploaded. - the daemon-lifecycle shell blob becomes scripts/lifecycle-e2e.sh: PATH-resolved minvmd (no cargo, macOS-reusable), temp workdir, and an EXIT-trap teardown so a failed assert cannot strand a daemon. - the krun_add_disk3 export assert moves into setup-libkrun-linux, mirroring the macOS composite and covering release.yml (which uses the composite but had no check); an early actionable failure - minvmd's compile-time link is the real backstop. Also per owner direction: workflow comments drop PR/issue and R-numbers (history belongs in commits, not YAML), and the DM labels are corrected against docs/specs/03-spec-networking (DM1 is the macOS model; this lane is DM3, session-e2e.sh header fixed accordingly). Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linux-kvm): reap harness leftovers before the daemon lifecycle step The lifecycle step spawns a fresh daemon just like the CLI session e2e, so it was equally exposed to leaked __krun-vmm/gvproxy children from a failed harness run. Move the reap to run immediately after the harness e2es, ahead of both fresh-daemon steps. Addresses CodeRabbit review on #702. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: cache discipline, slim CI debug info, and a --locked sweep Three cross-lane changes that must fork every cache exactly once: - rust-cache gains shared-key (one per job class that compiles the same artifacts: workspace/tests/clippy/native-e2e/mac-unit) and save-if restricted to main, via new setup-rust inputs forwarded from core-tests. PR branches restore but never write, so PR churn stops LRU-evicting the main caches every PR restores from (the 10 GB pool is shared repo-wide). - the two multi-GB raw actions/cache users (KVM build-linux, macOS artifacts stage2) split into cache/restore + main-only cache/save guarded on cache-hit. - CARGO_PROFILE_DEV_DEBUG=line-tables-only in CI (setup-rust env step; explicit job env on the mac and KVM jobs that bypass the composite): usable backtraces, smaller target trees and caches, faster links - without a named Cargo profile, which would move output out of target/debug and break codesign/testbed/justfile paths. - --locked on every workflow cargo invocation (build/test/clippy/ archive), so a stale Cargo.lock fails loudly instead of silently re-resolving; previously only core-tests' cargo fetch enforced it. Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: flip every lane to always-trigger with in-workflow gates The last two trigger-level path filters (ci.yml's paths-ignore + ci-macos.yml/ci-shell-installer.yml's paths) become dorny/paths-filter changes jobs with if: always() aggregators, completing the lane pattern the native and KVM lanes established. This unblocks the ruleset flip: a required context a trigger filter skips stays Expected forever and wedges the PR; a skipped in-workflow job reports skipped, which the aggregators treat as pass. - ci.yml: changes job (code = everything minus md/docs/LICENSE, the old paths-ignore inverted); all six jobs gated; ci-success skip-tolerant. - ci-macos.yml: trigger paths move into a changes filter (keeping the rust-toolchain.toml entry); artifacts/e2e/unit gated (the e2e keeps the RUN_MACOS_CI kill-switch, ANDed); NEW ci-macos-success aggregator - the fifth and final required-check context. - ci-shell-installer.yml: same flip; aggregator now skip-tolerant. - ci-docs-skip.yml DELETED: the inverse-path hack (and its wedge and silent-ungate failure modes) is obsolete - docs-only changes now skip jobs inside always-running workflows. - ci-gvproxy.yml DELETED: pin verification lives at point-of-use (fetch-gvproxy.sh SHA-checks in the KVM lane and release); vendor/gvproxy/** joins the KVM changes filter so a pin bump re-runs the lane that consumes it. - workflow comments drop remaining PR/issue and R-numbers (owner direction: history belongs in commits, not YAML). The ruleset itself still requires only ci-success; flipping it to the five aggregator contexts happens after the soak. Refs: #687 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: gate the new path filters to pull_request events Same fix as the lane hotfix: dorny cannot diff push events against our shallow, credential-free checkouts; pushes to main and dispatches run everything, PRs keep path economy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: persist-credentials false on every checkout The changes jobs hardened their checkouts but the pre-existing job checkouts still left the token in git config; make it uniform across ci.yml and ci-shell-installer.yml. No job in either workflow needs git credentials after checkout. Addresses CodeRabbit review on #712. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…eatures Audit of docs/reference/ against the code: - cli.md: Linux installs ship min/mip/minimald only; minvmd is a source build (prebuilt amd64 attached to GitHub Releases, no arm64) — per scripts/stage-release.sh COMPONENTS and release-pipeline.md. - minimal-dot-toml.md: rename stale `minimal update`/`minimal materialize` to the mip CLI, and cross-reference that `mip update` rewrites the upstream and sideload locked_commit fields in place (crates/op/src/project/update.rs). - tasks.md: mip run in the args example; document `description` (shown by mip status), the /bin/<cmd> exec resolution, arg defaults making arguments optional, and the full arg datatype forms (arrays, enums, table form with help/default) per the args crate. - build-specs.md: replace the line-anchored stdlib blob link with a stable file link; tests run by `mip check`. - harness-specs.md: `mip build` / `mip init` binary names (light re-audit after #813). - sandbox-operations.md: match the in-sandbox helper (crates/mctx/src/min_helper.sh + env.rs): --session flag and no-flag default, stack.* not harness.* targets, check takes --stacks (not --harnesses) and has no --skip-checkers/short flags, add the build/test shorthands and patched-pkg, mark the surface Linux-only, and add a note disambiguating the helper from the min session CLI. - cli-min.md, cli-mip.md: regenerate the snippets touched by the help text fixes (cache clean, login example) from the rebuilt binaries at d9f2016. - crates/minvmd/README.md: replace the stale "persistent data disk is a follow-up" note with the shipped per-VM /dev/vdb ext4 volume (crates/minvmd/src/volume.rs, spec 08) and update the libkrun guidance for the pinned source build (#694), keeping brew as the local-dev fallback. - frontmatter: every reference page now carries title + description. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What
PR2 of the CI refactor (#687), stacked on #685 (base =
ci/shared-composites; retarget tomainafter #685 merges).Closes the split where CI tested against the slp/krun Homebrew bottle while the release shipped our own trimmed source build that nothing executed before users got it. Both now use one build:
vendor/libkrun.lock(invendor/libkrun/) pins the containers/libkrun version and its resolved commit (v1.19.4/728df812), mirroring thegvproxy.lockpattern. Replaces theLIBKRUN_REFenv pin; the build fetches the commit directly, so a moved or deleted tag can never change what we build.scripts/build-libkrun-macos.sh <prefix>: shallow-fetch at the pin → trimmedcargo build -p libkrun --no-default-features --features blk,net→ self-containment assert (no/opt/homebrew///usr/localdeps) →krun_add_disk3(≥ 1.19.0) API-floor assert →install_name_tool -id @rpath/libkrun.1.dylib→ ad-hoc sign → stagelibkrun.1.dylib+libkrun.dyliblinker symlink.setup-libkrun-macoscomposite reworked: build-on-miss into a commit-keyed prefix (~/.cache/minimal-ci/libkrun/<commit>). GitHub-hosted runners rideactions/cache(key = commit + script hash + arch); the persistent mini just keeps the directory, so the source build happens once per pin bump. Verifies the staged dylib, publishesLIBKRUN_PREFIX.build-libkrun-macos-arm64ships from the same composite — the shipped dylib is by construction the one every macOS CI lane linked and booted. Developer ID signing flow untouched (#680); the dylib now arrives ad-hoc signed instead of unsigned.minvmd'sbuild.rsprefersLIBKRUN_PREFIXover its/opt/homebrewfallback and bakes the prefix as an rpath, so a leftover brew keg on the mini is ignored (and the installed-but-unlinked keg failure mode dies with it). Local dev is unaffected (the/opt/homebrewfallback remains).vendor/libkrun/**added toci-macos.ymlpaths: a pin bump rebuilds and re-runs the hypervisor e2e.A bonus simplification: with the
@rpathinstall name, minvmd records@rpath/libkrun.1.dylibat link time, sorewrite-macos-linkage.sh's-changebecomes a natural no-op — only the@loader_path → @loader_path/../libretarget still mutates release binaries.Verification (done locally on arm64 macOS before pushing)
scripts/build-libkrun-macos.shran end-to-end: fetched728df812, built the trimmed dylib (deps: Hypervisor.framework, libiconv, libSystem only), all asserts passed, staged into the versioned prefix.LIBKRUN_PREFIX=<prefix> cargo build -p minvmd→otool -Lshows the@rpath/libkrun.1.dylibload command;otool -lshows@loader_path+ prefix rpaths; the binary loads (minvmd --help), i.e. dyld resolves the staged dylib.actionlintfinding set identical to the base branch.CI on this PR exercises the composite on the mini (
e2e,build-macos); the release path (GH-hosted build + cache + Developer ID re-sign) gets exercised by the nightly once merged — worth watching that first run.🤖 Generated with Claude Code
Summary by CodeRabbit
Build & Release
CI
Performance & Quality