Skip to content

ci(macos): consolidate libkrun on our own pinned source build - #694

Merged
norrietaylor merged 18 commits into
mainfrom
ci/libkrun-own-build
Jul 10, 2026
Merged

ci(macos): consolidate libkrun on our own pinned source build#694
norrietaylor merged 18 commits into
mainfrom
ci/libkrun-own-build

Conversation

@norrietaylor

@norrietaylor norrietaylor commented Jul 9, 2026

Copy link
Copy Markdown
Member

What

PR2 of the CI refactor (#687), stacked on #685 (base = ci/shared-composites; retarget to main after #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 (in vendor/libkrun/) pins the containers/libkrun version and its resolved commit (v1.19.4 / 728df812), mirroring the gvproxy.lock pattern. Replaces the LIBKRUN_REF env 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 → trimmed cargo build -p libkrun --no-default-features --features blk,net → self-containment assert (no /opt/homebrew///usr/local deps) → krun_add_disk3 (≥ 1.19.0) API-floor assert → install_name_tool -id @rpath/libkrun.1.dylib → ad-hoc sign → stage libkrun.1.dylib + libkrun.dylib linker symlink.
  • setup-libkrun-macos composite reworked: build-on-miss into a commit-keyed prefix (~/.cache/minimal-ci/libkrun/<commit>). GitHub-hosted runners ride actions/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, publishes LIBKRUN_PREFIX.
  • 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 untouched (#680); the dylib now arrives ad-hoc signed instead of unsigned.
  • brew/slp-krun leaves the CI path entirelyminvmd's build.rs prefers LIBKRUN_PREFIX over its /opt/homebrew fallback 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/homebrew fallback remains).
  • vendor/libkrun/** added to ci-macos.yml paths: a pin bump rebuilds and re-runs the hypervisor e2e.

A bonus simplification: with the @rpath install name, minvmd records @rpath/libkrun.1.dylib at link time, so rewrite-macos-linkage.sh's -change becomes a natural no-op — only the @loader_path → @loader_path/../lib retarget still mutates release binaries.

Verification (done locally on arm64 macOS before pushing)

  • scripts/build-libkrun-macos.sh ran end-to-end: fetched 728df812, built the trimmed dylib (deps: Hypervisor.framework, libiconv, libSystem only), all asserts passed, staged into the versioned prefix.
  • LIBKRUN_PREFIX=<prefix> cargo build -p minvmdotool -L shows the @rpath/libkrun.1.dylib load command; otool -l shows @loader_path + prefix rpaths; the binary loads (minvmd --help), i.e. dyld resolves the staged dylib.
  • shellcheck clean on the script; actionlint finding 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

    • macOS builds and releases now provision libkrun using a pinned, reproducible “own build” process, aligned across CI and release steps.
    • Improved staging/signing to ensure the packaged dylib is taken from the pinned build output.
  • CI

    • macOS workflows now trigger on changes to the vendored libkrun pin, keeping rebuilds and E2E coverage in sync.
    • Split pre-test builds and added a link validation check to confirm minimal links only system libraries.
  • Performance & Quality

    • Added build caching for the pinned libkrun artifacts on supported runners.
    • Strengthened verification of the produced dylib (expected exports/interface and required symbol).

norrietaylor and others added 10 commits July 9, 2026 08:28
…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>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

macOS CI and release workflows now provision libkrun from the commit pinned in vendor/libkrun/libkrun.lock. A build script validates, signs, and stages the dylib, while the shared action adds cache-keyed reuse and verification.

Changes

Pinned libkrun provisioning

Layer / File(s) Summary
Build and stage the pinned dylib
scripts/build-libkrun-macos.sh
Fetches the locked commit, builds a trimmed dylib, validates dependencies and _krun_add_disk3, sets its install name, signs it, and stages the outputs.
Cache and verify provisioning
.github/actions/setup-libkrun-macos/action.yml
Resolves the lockfile pin, caches commit- and script-keyed builds, runs the build script on misses, verifies the dylib, and exports LIBKRUN_PREFIX.
CI and release integration
.github/workflows/ci-macos.yml, .github/workflows/release.yml
Triggers macOS CI for vendored libkrun changes, separates E2E builds, verifies system-only linking, and uses the pinned-build action for release staging.

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
Loading

Possibly related issues

  • gominimal/inbox#269: Directly covers consolidating macOS CI and release workflows around a pinned, cached, trimmed libkrun build.

Possibly related PRs

  • gominimal/minimal#655: Builds and validates a trimmed pinned macOS libkrun dylib in the release flow.
  • gominimal/minimal#659: Uses a trimmed Cargo-built libkrun dylib with link validation in the macOS release flow.
  • gominimal/minimal#678: Directly overlaps with shared macOS libkrun provisioning and verification changes.

Suggested reviewers: twitchyliquid64

Poem

I’m a rabbit with a pinned little hare,
Building dylibs with meticulous care.
Cache the commit, sign the prize,
Check the symbols under macOS skies—
Then hop it to release, neat and wise!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: macOS CI and release now use a pinned source-built libkrun instead of the Homebrew bottle.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

norrietaylor and others added 3 commits July 9, 2026 14:54
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>
Base automatically changed from ci/shared-composites to main July 9, 2026 23:07
#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>

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
scripts/build-libkrun-macos.sh (1)

68-69: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add --locked to 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 update Cargo.lock if 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

📥 Commits

Reviewing files that changed from the base of the PR and between 790a422 and 7c4704f.

⛔ Files ignored due to path filters (1)
  • vendor/libkrun/libkrun.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • .github/actions/setup-libkrun-macos/action.yml
  • .github/workflows/ci-macos.yml
  • .github/workflows/release.yml
  • scripts/build-libkrun-macos.sh

Comment thread .github/actions/setup-libkrun-macos/action.yml
- 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>
@norrietaylor

Copy link
Copy Markdown
Member Author

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 cargo build --locked (upstream's committed Cargo.lock verified present at the pin). Re-verified locally: the --locked build completes and stages into the hash-suffixed prefix.

norrietaylor and others added 2 commits July 9, 2026 17:42
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>

@coderabbitai coderabbitai 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.

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 win

Include the libkrun lockfile in both path filters.

vendor/libkrun.lock is not matched by vendor/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 win

Clean up the per-run state directory.

mktemp -d creates a fresh directory on every persistent-runner execution, but the EXIT cleanup only stops minvmd. Remove $XDG_STATE_HOME after 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2c3ff8f and 3a49c4d.

📒 Files selected for processing (1)
  • .github/workflows/ci-macos.yml

Comment on lines +196 to +207
- 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

@norrietaylor
norrietaylor merged commit 7777fa8 into main Jul 10, 2026
12 of 13 checks passed
@norrietaylor
norrietaylor deleted the ci/libkrun-own-build branch July 10, 2026 01:20
norrietaylor added a commit that referenced this pull request Jul 10, 2026
#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>
norrietaylor added a commit that referenced this pull request Jul 10, 2026
…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>
norrietaylor added a commit that referenced this pull request Jul 10, 2026
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>
norrietaylor added a commit that referenced this pull request Jul 10, 2026
* 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>
norrietaylor added a commit that referenced this pull request Jul 10, 2026
* 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>
norrietaylor added a commit that referenced this pull request Jul 10, 2026
* 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>
norrietaylor added a commit that referenced this pull request Jul 18, 2026
…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>
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.

2 participants