Skip to content

fix(ci): macOS kpm-build flake — Swatinem/rust-cache restores stale cargo shim pointing to rustup-init #134

Description

@kalwalt

Summary

The kpm-build (macos-latest) job in .github/workflows/ci.yml flakes intermittently with rustup-init: unexpected argument 'build' found when Swatinem/rust-cache@v2 restores a cache containing a stale cargo shim. The cached shim overwrites the fresh binary just installed by dtolnay/rust-toolchain@stable, and subsequent cargo build invocations are interpreted as rustup-init build.

Linux and Windows are unaffected.

Environment

  • Workflow: .github/workflows/ci.yml job kpm-build, matrix macos-latest
  • Runner: GitHub-hosted macOS arm64 (Darwin-arm64)
  • Actions:
    • dtolnay/rust-toolchain@stable
    • Swatinem/rust-cache@v2

Reproduction Steps

Non-deterministic — depends on cache state of the runner. Observed on PR #130 (commit pushed twice on the same branch):

  1. Push to a branch that triggers kpm-build.
  2. First run (#25870337001 at 15:56 UTC) — green
  3. Re-trigger the workflow on the same commit (#25870675080 at 16:03 UTC) — macOS failed, Linux+Windows green
  4. Re-run the failed job — green again ✅ (cache rebuilt from scratch)

Expected Behavior

cargo build -p webarkitlib-rs --features ffi-backend runs the toolchain installed by dtolnay/rust-toolchain@stable.

Actual Behavior

$ cargo build -p webarkitlib-rs --features ffi-backend
error: error: unexpected argument 'build' found

Usage: rustup-init[EXE] [OPTIONS]

For more information, try '--help'.
Process completed with exit code 1.

The cargo binary on PATH resolves to rustup-init (the bootstrap installer), not real cargo. This happens after Swatinem/rust-cache@v2 restores /Users/runner/.cargo/bin/ from cache key v0-rust-kpm-build-Darwin-arm64-9d946206-575a3e0f.

Error Details

2026-05-14T16:03:27.8362710Z ##[group]Run cargo build -p webarkitlib-rs --features ffi-backend
2026-05-14T16:03:27.9682690Z error: error: unexpected argument 'build' found
2026-05-14T16:03:27.9683270Z
2026-05-14T16:03:27.9684700Z Usage: rustup-init[EXE] [OPTIONS]
2026-05-14T16:03:27.9700800Z ##[error]Process completed with exit code 1.

Failing run: https://github.com/webarkit/WebARKitLib-rs/actions/runs/25870675080/job/76024750833?pr=130

Root Cause

Workflow step order:

  1. actions/checkout@v5
  2. dtolnay/rust-toolchain@stable — installs cargo to /Users/runner/.cargo/bin/cargo
  3. Swatinem/rust-cache@v2 — restores /Users/runner/.cargo/bin from cache (this step overwrites the fresh cargo binary)
  4. cargo build … — fails

On macOS, the cache occasionally contains a cargo shim that points to rustup-init (the Rust bootstrap installer). When restored, it shadows the real binary. This is a known interaction between rust-cache and toolchain managers — the rust-cache step does not currently exclude /Users/runner/.cargo/bin from restoration when the toolchain is freshly installed.

Impact

Medium — blocks PR CI on macOS approximately every Nth run. Workaround (re-run failed job) is trivial but recurring.

Suggested Mitigations

In order of preference / non-invasiveness:

Option A — Pin cache key to the toolchain version (minimal change)

- name: Install Rust toolchain
  id: rust
  uses: dtolnay/rust-toolchain@stable

- name: Rust Cache
  uses: Swatinem/rust-cache@v2
  with:
    prefix-key: "v0-rust-${{ steps.rust.outputs.cachekey }}"

This invalidates the cache when the Rust toolchain version changes, preventing stale cargo shims from surviving across toolchain updates.

Option B — Exclude .cargo/bin from cache

- name: Rust Cache
  uses: Swatinem/rust-cache@v2
  with:
    cache-bin: false

cache-bin: false (available in Swatinem/rust-cache ≥ 2.5.0) prevents caching of .cargo/bin. Tradeoff: slightly slower for cargo-installed tools (e.g. cargo-binstall), none of which the project uses today.

Option C — Sanity-check cargo after cache restore (defensive)

- name: Verify cargo is real
  run: |
    cargo --version || (echo "cargo binary is broken; reinstalling" && rustup default stable)

Catches the symptom but doesn't address the root cause. Useful as a belt-and-braces alongside A or B.

Recommendation

Apply Option A (cache key pinned to toolchain version) as the first fix — minimal, addresses the documented race. If flakes recur, layer Option B on top.

Visual Evidence

Job comparison from the same workflow run #25870675080:

Job Result
kpm-build (ubuntu-latest) ✅ pass
kpm-build (windows-latest) ✅ pass
kpm-build (macos-latest) ❌ fail (then pass on re-run)

Additional Context

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Done

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions