# A Rust build cache in GitHub Actions

A Rust job in GitHub Actions starts with nothing compiled, so every run compiles every crate again. With hushrun's shared store, a job downloads the crates your `main` branch's CI already compiled instead, and so do teammates' laptops and coding agents' sandboxes, while a pull request can reuse that code but never change it.

If you only need one repository's CI warm, and no laptops or sandboxes, a plain cache may be enough: see [Without hushrun](#without-hushrun).

## Before you begin

- hushrun 0.15.0 or later, on every machine and in CI.
- The repository on GitHub Actions, connected with `npx hushrun init`, which also gives its jobs `id-token: write`.
- An admin or owner of your organization on hushrun, for the steps in the dashboard.
- One Rust toolchain for laptops and CI, pinned in `rust-toolchain.toml`. A machine reuses only code built for its own system and compiler version, so a Mac gets nothing from Linux CI.

## Run your cargo commands through hushrun

Only `hushrun run NAME -- COMMAND` uses the shared store. `npx hushrun init` wraps the cargo lines it finds in your workflows, or you write them yourself:

```yaml title=".github/workflows/ci.yml"
jobs:
  test:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v5
      - uses: hushrun/hushrun/action/setup@v0.15.0
      - run: hushrun run test -- cargo test --locked
      - run: hushrun run build -- cargo build --locked
```

The job needs `id-token: write`: it asks the shared store as itself, with the job's ID token, and without one it shares nothing. The backend comes from the `hushrun.yaml` that init writes; without one, set `HUSHRUN_BACKEND` in the job's `env` ([Set up CI](https://hushrun.com/docs/ci.md#by-hand)).

The same line also skips the whole check when these exact files already passed it ([How it works](https://hushrun.com/docs/run.md)). Clippy, rustdoc and coverage builds can go through hushrun too, but their compiles aren't reused.

## Take out other compile caches

In the jobs that run through hushrun:

- Remove `RUSTC_WRAPPER`, and sccache's setup step if it sets it. Where it's set, hushrun's build reuse turns itself off for that run, and says so in one line.
- Stop caching `target/` between jobs. A restored folder proves nothing about how its files were built, so hushrun shares only what the job compiled itself.
- Caching cargo's registry, `~/.cargo`, is fine.

Then push to `main`, so its CI runs hushrun once.

## Confirm the repository and trust main

In the dashboard's **Shared store** page, `https://app.hushrun.com/<org>/-/build`:

1. Under **Repositories**, open the repository, which shows as *seen* once its CI has run hushrun, and choose **Confirm…**.
2. Under **Trusted branches**, choose `main` and **List…**. Its CI now writes the code every other machine reuses. List only branches whose pushes you trust, and protect them on GitHub.

The full rules, and what each choice trusts: [Share compiled code](https://hushrun.com/docs/shared-store.md#confirm).

## Turn sharing on, then reuse

1. Under **Sharing**, choose **Turn on…**. Sharing starts by watching: machines compile everything, upload what they build and compare it with the store, and reuse nothing.
2. Push to `main` and open a pull request or two. The **Usage** panel shows the comparisons, trusted CI's first.
3. Once trusted CI has compared crates and none differed, choose **Start reusing…**.

From then on, a job's last lines say what came from the store. This line is an example of the output, not a measurement:

```text
hushrun: build: 226 of 360 crates reused (226 from the shared store), 134 built, saving about 2m 3s
```

## Which jobs reuse what

| Job | Reuses | Writes |
|---|---|---|
| A push to `main`, once listed | `main`'s trusted code | `main`'s trusted code |
| A pull request | The trusted code, then what earlier runs of the same pull request built | Code for that pull request alone |
| A tag, or a job with an `environment:` key | The trusted code | Nothing |
| A pull request from a fork | Nothing | Nothing |

A job on a runner that isn't one of GitHub's own writes trusted code only after an admin trusts self-hosted runners ([why](https://hushrun.com/docs/shared-store.md#self-hosted)). The whole table, laptops and sandboxes included: [Whose code a machine reuses](https://hushrun.com/docs/compiled-code.md#whose-code).

## Laptops and sandboxes

- **A teammate's laptop** reuses the trusted code after `hushrun build on`, once per machine. It's off until then.
- **A coding agent's sandbox** with `HUSHRUN_AGENT` reuses it with nothing more, as long as it can reach the store's host: see [Warm sandboxes for coding agents](https://hushrun.com/docs/guides/warm-sandboxes-coding-agents.md).

Reuse runs one way: CI's code reaches laptops and sandboxes, and a laptop's never reaches CI.

## Without hushrun

A plain cache is the better fit when:

- your CI isn't GitHub Actions: hushrun's shared store doesn't take part in GitLab CI or other CI yet;
- you build on Windows, or compile C, C++ or CUDA as well, which hushrun's compile reuse doesn't cover;
- only CI needs to be warm, and you'd rather not involve an admin or a hosted store.

**Cache cargo's folders.** [`Swatinem/rust-cache`](https://github.com/Swatinem/rust-cache) caches `~/.cargo` and the dependencies' build output in `./target`, keyed on the compiler, `Cargo.lock`, the `Cargo.toml` files and the toolchain files. It doesn't cache your workspace's own crates.

```yaml title=".github/workflows/ci.yml"
steps:
  - uses: actions/checkout@v5
  - uses: Swatinem/rust-cache@v2
  - run: cargo test --locked
```

**Cache compiler output with sccache.** [`mozilla-actions/sccache-action`](https://github.com/Mozilla-Actions/sccache-action) installs sccache, and `SCCACHE_GHA_ENABLED` keeps its cache in GitHub's. It can't cache `bin`, `dylib`, `cdylib` or `proc-macro` crates, or incremental builds ([sccache's Rust notes](https://github.com/mozilla/sccache/blob/main/docs/Rust.md)).

```yaml title=".github/workflows/ci.yml"
jobs:
  test:
    runs-on: ubuntu-latest
    env:
      SCCACHE_GHA_ENABLED: "true"
      RUSTC_WRAPPER: sccache
    steps:
      - uses: actions/checkout@v5
      - uses: mozilla-actions/sccache-action@v0.0.11
      - run: cargo test --locked
```

Both store in GitHub's cache, which holds [10 GB per repository by default](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching) and drops what's unused for 7 days. A run restores caches from its own branch and the default branch, and a pull request also from its base branch. More: [hushrun and sccache](https://hushrun.com/docs/compare/sccache.md).

## Limits

- **Only libraries are shared.** Programs, tests, proc macros and build scripts are compiled on each machine; a machine's own store still reuses them.
- **New code** compiles once, wherever it's first built.
- **`hushrun run CHECK`**, the checks of `hushrun.yaml`, uses no shared code in CI. Use `hushrun run NAME -- COMMAND`.
- **No speed figure.** hushrun hasn't published a measurement of the shared store yet. A download starts only when it should arrive within half the time the crate takes to compile; otherwise the compiler runs ([Compiled code](https://hushrun.com/docs/compiled-code.md#shared-store)).

## Related

- [Share compiled code](https://hushrun.com/docs/shared-store.md): every step and setting.
- [hushrun and sccache](https://hushrun.com/docs/compare/sccache.md)
- [hushrun on CI](https://hushrun.com/ci): what CI stops doing again.
- [Reduce your GitHub Actions costs](https://hushrun.com/docs/guides/reduce-ci-costs.md)
