This guide describes the GitHub Actions pipeline configured in .github/workflows/ci.yml. It is the first external gate beyond self-review and agent-review. When the CI is red, the commit is not mergeable, even if local checks passed.
| Job | Toolchain | Wall time (expected) | Fails on |
|---|---|---|---|
lint-and-host-test |
pinned nightly + rustfmt + clippy |
~2 min | cargo fmt --check diff, any clippy warning, any failing host test |
kernel-build |
pinned nightly + aarch64-unknown-none + clippy |
~1 min | cargo kernel-build error, any kernel-clippy warning |
host-stable-check |
stable (no extra components) | ~2 min | host crates failing to build or test on stable Rust |
miri |
pinned nightly + miri component |
~10–15 min | Any Stacked Borrows violation in cargo +nightly miri test --workspace --exclude tyrne-bsp-qemu-virt |
coverage |
pinned nightly + llvm-tools-preview + cargo-llvm-cov |
~3–5 min | Never (informational only, continue-on-error: true) |
All jobs run on ubuntu-latest. Each caches cargo registry + build artefacts keyed by Cargo.lock hash, so warm runs are far faster than first runs.
The kernel needs nightly to build (inline asm intrinsics / lang items — see rust-toolchain.toml and ADR-0002). rust-toolchain.toml pins nightly-2026-01-15 at the repo root, and rustup's override precedence means that file selects the pinned nightly for every in-repo cargo invocation regardless of rustup default. So lint-and-host-test, kernel-build, miri, and coverage all select the pin explicitly (cargo +$NIGHTLY_PIN …) — the same toolchain a contributor's local cargo uses.
A genuine "the host-buildable crates compile and pass tests on stable Rust" signal. It runs cargo +stable build and cargo +stable host-test over the workspace default-members (kernel, hal, test-hal) — the bare-metal BSP is excluded because it needs nightly. The +stable prefix bypasses the rust-toolchain.toml override so this job exercises stable for real. It deliberately does not run clippy/fmt with -D warnings: clippy::pedantic is warn workspace-wide and stable is a rolling toolchain, so a future stable pedantic lint could redden the gate with no code change of ours — lint/format enforcement lives only on the pinned-nightly jobs. If a host crate ever grows a #![feature(...)] it does not strictly need, this job is the one that goes red on stable.
Every third-party GitHub Action is pinned to a full 40-character commit SHA with the human-readable version in a trailing comment (e.g. actions/checkout@11bd719… # v4.2.2). Tags are mutable; SHAs are not. This applies the same anti-silent-drift discipline used for NIGHTLY_PIN and cargo-llvm-cov to the actions that wrap them — taiki-e/install-action in particular downloads and executes a prebuilt binary, so a tag repoint would be arbitrary code execution in CI. See docs/standards/infrastructure.md §"Supply-chain security" → "GitHub Actions pinning" for the refresh path.
- Every push to
mainordevelopment. - Every PR targeting
mainordevelopment.
Concurrent runs on the same branch cancel each other — only the latest commit's verdict matters.
The CI matrix mirrors what a contributor should run locally before opening a PR:
| Local command | CI job |
|---|---|
cargo fmt --all -- --check |
lint-and-host-test (step 1) |
cargo host-clippy |
lint-and-host-test (step 2) |
cargo host-test |
lint-and-host-test (step 3) |
cargo kernel-build |
kernel-build |
cargo kernel-clippy |
kernel-build |
cargo +stable build + cargo +stable host-test (host crates) |
host-stable-check |
cargo +nightly miri test --workspace --exclude tyrne-bsp-qemu-virt |
miri |
cargo llvm-cov --workspace --exclude tyrne-bsp-qemu-virt --summary-only |
coverage |
If you pass these locally, CI should pass too. Your local cargo runs the pinned nightly automatically (the rust-toolchain.toml override), which is the same toolchain the kernel CI jobs use — so a local/CI divergence on those jobs is rare. If you see one, confirm your pinned nightly is installed (rustup toolchain list should show nightly-2026-01-15); rustup installs it on first use, but a stale or partial install can drift. For the host-stable-check job, reproduce with the explicit prefix: cargo +stable build (install stable first with rustup toolchain install stable if needed).
The BSP is a bare-metal no_std + no_main binary whose panic handler conflicts with std's panic_impl lang item when built for the host target (which Miri and llvm-cov both require). BSP code is exercised indirectly via the QEMU smoke test; automating that runs under CI is a T-009 follow-up (the timer init task) — once the kernel can produce a finish-signal, QEMU can exit non-zero on mismatch and CI can assert the trace.
Today coverage is informational: continue-on-error: true in the workflow. After T-011 closes (which raises sched/mod.rs past 90 % and the workspace past 96 %), the plan is to flip this job to enforce a floor. The floor should be slightly below the measured baseline so regressions trip the gate but normal churn does not.
- Add a job to
.github/workflows/ci.yml. - Keep the fast-lane job order stable —
lint-and-host-testmust remain first so red PRs fail quickly. - If the check requires a nightly feature, put it in its own job (not folded into
lint-and-host-test). - Update this guide's table.
Miri and cargo-llvm-cov use a pinned nightly declared via the NIGHTLY_PIN env var at the top of .github/workflows/ci.yml. Rolling nightly means that a miri or llvm-tools regression on the public nightly channel breaks CI without any commit of ours being the cause; pinning isolates us from that.
cargo-llvm-cov itself is also pinned — installed via taiki-e/install-action which downloads a prebuilt binary rather than compiling from source. The version is in the workflow as tool: cargo-llvm-cov@<x.y.z> (0.6.16 at the time of writing). Pinning the tool prevents an upstream release from silently changing what "coverage" reports.
Current pins:
NIGHTLY_PIN = nightly-2026-01-15(set 2026-04-23 when R6 landed).cargo-llvm-cov 0.6.16(set 2026-04-28; seecoveragejob inci.yml).
To bump either pin:
- Open an issue titled "Bump <pin> to <new-version>" stating the reason (new Miri check we want, a compiler feature we need, a cargo-llvm-cov bug fix, a security advisory, routine refresh).
- Update the
NIGHTLY_PINenv var or thetool: cargo-llvm-cov@…line in.github/workflows/ci.ymland the "Current pins" list above. - Run
cargo +nightly-YYYY-MM-DD miri test --workspace --exclude tyrne-bsp-qemu-virtandcargo +nightly-YYYY-MM-DD llvm-cov --workspace --exclude tyrne-bsp-qemu-virt --summary-onlylocally; make sure both are green on the new pin. - Land the pin bump in its own commit with
Refs:to the issue. The two pins can be bumped together or independently.
The coverage job is marked continue-on-error: true. GitHub's UI renders this as a neutral / yellow verdict rather than green or red. Be deliberate when configuring branch-protection rules:
- Do not add
coverageto the required-checks list while it is informational; the neutral result does not satisfyrequired == passing, so every push would be blocked even when coverage is fine. - Do add the four real gates to required checks. GitHub matches each job's display name, not its id, so add these exact strings:
fmt + clippy + host tests (nightly),aarch64-unknown-none kernel build (nightly),host crates on stable, andmiri (Stacked Borrows). - When coverage flips from informational to enforcing (planned post-T-011), remove
continue-on-error: truefirst, confirm a full run is green, then add the job to branch-protection.