---
title: Cache key
description: What goes into the blake3 cache key and why keys are portable across machines.
---

# Cache key

The cache key is a blake3 hash that uniquely identifies one compiled artifact. Two rustc invocations that would produce identical output must produce the same key. Two invocations that differ in any input that affects the output must produce different keys.

Getting this right is the core correctness challenge in any build cache. A false positive (same key, different output) corrupts the build. A false negative (different keys, identical output) wastes time.

## What's in the key

The principle is simple: **anything that changes the compiled output goes into the key; anything that's just machine-local noise is normalized out.** That's what lets one machine restore what another built.

What goes in (change any of these and the crate re-keys):

- **Toolchain identity** — the `rustc --version --verbose` banner (commit hash + host triple), plus the linker's `--version` for outputs that actually link.
- **What you're compiling** — crate name, types, and edition; and every source file the crate reads. kache finds them with a `--emit=dep-info` pre-pass, so modules, `include!()` targets, and build-script output are all covered, and hashes each by content.
- **How you're compiling it** — target triple, feature/`cfg` flags, codegen (`-C`) options, `RUSTFLAGS`, and the active `--emit` set (so a `cargo check` entry is never served to a `cargo build`).
- **What it depends on** — each `--extern` rlib/rmeta, hashed by content, so a dependency change propagates to everything downstream.
- **Compile-time environment** — the `env!()` / `option_env!()` values a build bakes in.

What's deliberately left out (machine-local, doesn't affect output): the incremental-compilation directory, the linker path (its *identity* is captured via `--version` instead), and absolute build/checkout paths — those are normalized to stable sentinels so the key is the same regardless of where you built. See [Cross-machine portability](#cross-machine-portability).

<Callout type="info">
The authoritative, always-current list for any specific build is what kache actually logs — `kache why-miss <crate>` and `KACHE_LOG=trace` print every component it hashed (see [Debugging cache misses](#debugging-cache-misses)). Prefer that over a hand-kept enumeration, which drifts as the keying logic evolves.
</Callout>

### Tuning the key

A few opt-in knobs, all no-ops by default:

- **`KACHE_BASE_DIR`** — declare a path prefix to strip from the key (collapsed to `<BASE_DIR>`). Use it for checkout or container-mount paths the automatic sentinels don't catch, so out-of-tree builds still share hits.
- **`KACHE_KEY_SALT` / `cache.key_salt`** — fold an opaque string into every key to force a cold cache when the toolchain changes in a way no `--version` reveals (a libc/linker bump, a Nix rebuild). See [Configuration](/getting-started/configuration#cache-key-salt).
- **`kache.toml` extra inputs** — declare files the compiler reads but never reports (sqlx's `.sqlx/`, `migrations/`, `include!`'d data) so editing them re-keys the crate. See [Configuration](/getting-started/configuration#extra-cache-key-inputs).
- **`path_only_env_vars`** — extra env vars whose value is only a path locator, so kache normalizes their path out of the key for cross-machine hits. See [Configuration](/getting-started/configuration#path-only-env-vars).

## Cross-machine portability

The key is designed to be the same on any machine that has the same:

- Rust toolchain version
- Target triple
- Source code
- Feature flags and cfg values
- RUSTFLAGS (after path normalization)

This is what makes the remote cache useful in CI: a developer's laptop and a fresh CI runner with the same toolchain produce identical keys, so CI can restore what the developer already built.

## Debugging cached binaries

The same `--remap-path-prefix` rules that make artifacts portable also rewrite the paths baked into debug info. A cached binary's DWARF (or PDB on Windows) refers to your sources through stable sentinels instead of the machine-local path they were compiled at:

| Sentinel | Real location |
|---|---|
| `<BASE_DIR>` | the `KACHE_BASE_DIR` prefix, if set (highest priority) |
| `<WORKSPACE>` | the repo / workspace checkout |
| `<CARGO_HOME>` | usually `~/.cargo` (registry + git deps) |
| `<RUSTUP_HOME>` | usually `~/.rustup` (std sources) |
| `<HOME>`, `<TMPDIR>`, `<TARGET>` | per-user / per-job directories |

On Windows the same machinery adds `<APPDATA>`, `<LOCALAPPDATA>` and `<PROGRAMFILES>` sentinels for cargo/rustup state and MSVC/SDK paths that flow into native-link search.

So a backtrace or a debugger opened against a restored binary will show source paths like `<WORKSPACE>/src/main.rs` and won't find the file on disk until you map the sentinel back. This is expected — it is the price of one binary being valid in every checkout — not a sign the cache is corrupt.

This page describes the **rustc** artifact cache. C/C++ object compiles (`cc` / `clang-cl`, see [C/C++ caching](https://github.com/kunobi-ninja/kache#cc-caching-experimental)) are keyed separately. clang-cl debug compiles (`/Z7` / `-Z7` / `-g`) *are* cached, but the key stays machine-local: clang-cl embeds CodeView paths directly in the `.obj` (no separate PDB) and ignores `-ffile-prefix-map`, so kache keeps those paths literal in the key rather than remapping them — correct local hits, no cross-machine sharing.

Point your debugger at the real checkout with a source map. For your own crate sources, map `<WORKSPACE>`:

```sh title="gdb"
# in ~/.gdbinit or at the prompt
set substitute-path <WORKSPACE> /abs/path/to/your/checkout
```

```sh title="lldb"
# in ~/.lldbinit or at the prompt
settings set target.source-map <WORKSPACE> /abs/path/to/your/checkout
```

Add additional `substitute-path` / `source-map` entries for `<CARGO_HOME>` or `<RUSTUP_HOME>` if you want to step into dependency or std sources.

**Coverage builds are exempt.** When coverage instrumentation is active (`-C instrument-coverage`), kache skips path remapping entirely, so tools like `cargo-llvm-cov` and tarpaulin map profiling data back to real source paths with no extra configuration. (Skipping the remap also changes the key, so coverage builds keep a separate cache from regular ones.)

## Debugging cache misses

If a crate keeps missing when you expect a hit, use `kache why-miss <crate-name>`. It compares the cache key from the last known entry against the current build and reports which input changed.

For full key-component details, run the command `why-miss` prints:

```sh
KACHE_LOG=trace cargo build -p <crate-name> 2>&1 | grep '\[key:<crate-name>\]'
```

This logs every component being hashed during key computation. As a defense-in-depth check, a `KACHE_LOG=warn` run also flags any residual machine-local absolute path that survives normalization (matching prefixes like `/Users/`, `/home/`, `/private/tmp/`, `/var/folders/`, `C:\Users\`), naming the offending key field — a fast signal that something is leaking a per-machine path into the key.
