---
title: Command reference
description: Every top-level Kache command, its purpose, and common flags
---
Run `kache <command> --help` for the exact syntax in your installed version.
`--json` emits one versioned JSON document for `list`, `stats`, `gc`, `clean`, `targets`, `doctor`, `why-miss`, and `daemon status`. `clean --json` is a preview unless `--yes` is also present.
| Command | Purpose |
| --- | --- |
| `cargo` | Run Cargo with duplicate Cargo-home rustflags collapsed |
| `list` | Inspect local entries |
| `gc` | Evict local entries |
| `purge` | Remove local cache entries |
| `clean` | Remove Cargo target directories below cwd or from the local tracked-root registry |
| `targets` | Show tracked target directories: size, what deleting each frees, and whether its worktree still exists |
| `init` | Configure Cargo, optional C/C++ shims, test pacing, and the daemon service |
| `doctor` | Diagnose setup and store integrity |
| `sync` | Pull from and push to a remote |
| `save-manifest` | Publish build metadata for later prefetch |
| `daemon` | Inspect and manage the background daemon |
| `monitor` | Open the live dashboard |
| `stats` | Print a non-interactive summary |
| `telemetry` | Write cache counters and this machine's shared-cache state as OTLP JSON for Kartero |
| `why-miss` | Explain a crate's recent key change |
| `report` | Produce text, Markdown, JSON, or trace output |
| `config` | Open the configuration editor |
| `login` | Log in to the planner with a Kunobi account |
| `logout` | Log out of the planner and revoke the stored session |
| `install-shims` | Create Unix compiler-name symlinks |
| `test-runner` | Run a test binary as Cargo's target runner, paced by the scheduler |
| `completions` | Generate shell completion scripts |
## `kache cargo`
```bash
kache cargo -- build
kache cargo -- test --workspace
```
Pass Cargo arguments after `--`. On Cargo 1.91 or newer, this command can keep intermediate worktree state separate while coordinating a shared final target directory.
## `kache list`
```bash
kache list
kache list serde
kache list --sort size --no-pager
```
Sort values are `name`, `size`, `hits`, and `age`.
Interactive output uses `KACHE_PAGER`, then `PAGER`, then a platform default. `--no-pager` writes directly.
## `kache gc`
```bash
kache gc
kache gc --max-age 7d
kache gc --stale-schema
```
A bare run enforces configured size and age policies. `--max-age` runs the requested age policy. `--stale-schema` removes entries created with old or unrecorded key schemas and conflicts with `--max-age`.
GC affects only the local store. It also sweeps each `[cache.volumes]` shard against that shard's own budget; see [Configuration: Volume shards](/docs/getting-started/configuration#volume-shards). `--stale-schema` sweeps only the main store, and the `--json` document reports the main store's disk use and entry count.
GC reports store bytes removed separately from bytes reclaimed on disk. It keeps an entry when its last store blob is still cloned into a build output, because unlinking that blob would free no disk and destroy a usable hit. This can leave the registered store size above `cache.local_max_size`; remove stale targets to reclaim the blocks. A size sweep leaves those held bytes out of its budget instead of evicting other entries to make up for them.
Set `cache.gc_evict_shared = true` or `KACHE_GC_EVICT_SHARED=1` to restore the older namespace-first policy. That lowers the registered store size but does not reclaim blocks retained by target directories, and the discarded entry becomes a future miss. See [Configuration: GC and shared build outputs](/docs/getting-started/configuration#gc-and-shared-build-outputs).
GC flushes any entries still pending durability before it judges them (see [Architecture](/docs/how-it-works/architecture)).
## `kache purge`
```bash
kache purge --crate-name serde
kache purge
```
With no crate name, `purge` wipes the whole local store at once. It does not ask for confirmation.
## `kache clean`
```bash
kache clean
kache clean --dry-run
kache clean --yes
kache clean --tracked --stale 14d --dry-run
kache clean --tracked --stale 14d --yes
kache clean --orphans --yes
```
`clean` recursively finds `target/` directories below the current directory, including worktrees under `.worktrees/` and `.claude/`; other hidden directories are skipped. Sizes cover every profile, custom profiles and `<triple>/<profile>` directories included. The default opens a selector. Use `--dry-run` before a scripted `--yes` run.
`--tracked` uses the bounded machine-local target registry populated by normal builds and restores. `--stale` filters by last use; without it, `--tracked` selects targets unused for 14 days. `--orphans` implies `--tracked`. A target whose worktree or workspace has been deleted qualifies however recently it was built; `--orphans` selects only those. A workspace counts as deleted only while the directory that held it still exists, so an unmounted volume does not.
Cleanup rechecks each directory's identity and removes only the derived target directory. It skips paths that are missing, moved, replaced or unsafe, and targets of the current workspace. A target that a running build holds (its `.cargo-lock`) is skipped with `in use by a running build`, in every mode.
The daemon removes tracked targets whose workspace was deleted on its own, and idle ones when configured. See [Target directory cleanup](/docs/getting-started/configuration#target-directory-cleanup).
## `kache targets`
```bash
kache targets
kache targets --json
```
`targets` lists every tracked target directory that still exists: its workspace, how long since a build last used it, the bytes it holds, and an estimate of what deleting it frees. The estimate leaves out blocks the target shares with the store or with other hardlinks, so it is what `kache clean` would give back. A target whose worktree has been deleted is marked. The text output ends with the command that removes those targets; `--json` gives it in `next`.
## `kache init`
```bash
kache init
kache init --check
kache init --no-service
kache init --no-shell
kache init --yes
```
`--check` reports proposed changes without writing. `--no-service` skips login-service installation. `--no-shell` skips the C/C++ shell setup; Cargo's native dependencies are still configured. When the service cannot be installed, such as on a Linux host without a systemd user session or a Windows account that Task Scheduler refuses, init reports why and starts the daemon directly.
Without a terminal, pass `--yes`. If stdin has no answers and Cargo is left unconfigured, init exits with an error. Besides `rustc-wrapper`, init adds `HOST_CC` / `HOST_CXX` / `CC_KNOWN_WRAPPER_CUSTOM` under Cargo `[env]` when those keys are absent. On Windows it adds only `CC_KNOWN_WRAPPER_CUSTOM`. It never sets `CC` or `CXX`.
On Unix, init offers to pace test binaries: it exports `CARGO_TARGET_<HOST>_RUNNER="kache test-runner"` from the same shell startup files the C/C++ setup edits, so `cargo test` in every project waits for the scheduler's slots. A value you already export stays. `--no-shell` skips both edits. See [Test runner](/docs/getting-started/configuration#test-runner).
## `kache doctor`
```bash
kache doctor
kache doctor --fix
kache doctor --verify
kache doctor --checksums
kache doctor --repair
```
- `--fix` repairs supported setup issues and can migrate sccache configuration.
- `--purge-sccache` also removes sccache data and requires `--fix`.
- `--verify` checks entries, blobs, and metadata.
- `--checksums` also hashes blob contents and implies `--verify`.
- `--repair` removes corrupted entries, reconciles the index with the entries on disk, reclaims orphan blobs, prunes stale C/C++ memos, input predictions and old file hashes, and compacts a large, sparse index when disk space allows. It implies `--verify`.
`--purge-sccache` and `--repair` delete data. Check what they will remove before running them.
Doctor also reports where the running binary is installed ("Install path"), whether the C/C++ shims are active, broken, not first on `PATH` or not installed, and whether the login service runs a binary that no longer exists, is not executable, or is a different file from the running Kache. Run `kache daemon install` to fix the last one.
With a remote configured, `kache doctor` also asks it for an object that is never written. A miss passes: the store answered and accepted the credentials. A failure names the likely cause when the store says, such as expired credentials, a secret key that does not match, a clock too far from the store's, the bucket's real region, or a bucket that does not exist. An S3 endpoint that uses plain `http://` to another machine fails its own check.
## `kache sync`
```bash
kache sync
kache sync --pull
kache sync --push
kache sync --dry-run
kache sync --pull --workspace
kache sync --pull --all
```
Options:
| Flag | Effect |
| --- | --- |
| `--manifest-path <PATH>` | Select Cargo manifest for push-side workspace filtering |
| `--pull` | Skip uploads |
| `--push` | Skip downloads |
| `--dry-run` | Plan without transfer |
| `--all` | Ignore normal pull filtering |
| `--workspace` | Scope pull discovery to workspace members; conflicts with `--all` |
| `--allow-partial` | Exit successfully despite reported transfer/import failures |
See [Sync](/docs/remote-cache/sync) for filtering semantics.
## `kache save-manifest`
```bash
kache save-manifest
kache save-manifest --manifest-key x86_64-linux-release
kache save-manifest --namespace x86_64-unknown-linux-gnu/rustc-hash/release
```
Without `--manifest-key`, Kache publishes an identity key
(`id/<lock-digest>/<target>/<profile>` when `Cargo.lock` and a profile are
known) and the host target triple so older prefetch still finds the file.
`KACHE_MANIFEST_KEY` or `--manifest-key` writes a single key. A namespace plus
`Cargo.lock` also publishes content-addressed shards. Build-script runs are left
out: they stay in the local store, so no other machine could fetch them.
When the remote has no manifest for the current lockfile, the planner tries
the manifests of up to three earlier committed revisions of `Cargo.lock`
before the host-triple key, so a dependency bump still prefetches the part of
the graph it did not change. This reads git history: a shallow clone needs
enough of it (`fetch-depth` on GitHub Actions), and without git the step is
skipped.
CI jobs that share a key, such as tests and lints on the same lockfile, target
and profile, add to one manifest rather than replacing each other. Entries from
the same commit (`GITHUB_SHA` or `CI_COMMIT_SHA`) stay together, entries from
an earlier commit are dropped when a later one publishes, and entries whose
commit is unknown are kept for an hour. On S3 the update is a conditional
write: a job that loses a race reads the manifest again and retries, and after
five tries it fails and leaves the manifest as the other jobs wrote it. Stores
without conditional writes, including filesystem remotes, get the merged
manifest written plainly, so two jobs finishing at the same moment can still
drop each other's entries there. Shards merge the same way.
## `kache daemon`
```bash
kache daemon # status
kache daemon status
kache daemon run
kache daemon start
kache daemon stop
kache daemon restart
kache daemon install
kache daemon uninstall
kache daemon log
```
See [Daemon lifecycle](/docs/daemon/lifecycle).
## `kache monitor`
```bash
kache monitor
kache monitor --since 7d
```
`--since` takes the same forms as `kache stats`. Without it, the monitor loads the last hour.
See [Monitor](/docs/monitor).
## `kache stats`
```bash
kache stats
kache stats --since 15m
kache stats --since 2h
kache stats --last-build --root "$PWD"
```
`--since` accepts `s`, `m`, `h`, and `d` units (`90s`, `15m`, `2h`, `7d`). A bare number is hours. The default is `24h`. The counters and the `last ...` label both describe the requested window; a value that does not parse is an error.
`--last-build` shows the detailed report for the latest recorded activity session. Add `--root` to choose a build tree; without it, the latest event across all roots chooses the session. It cannot be combined with `--since`. With `--json`, the stats envelope contains a `report` object using the build report schema below.
`kache report --record` also appends one JSON line to `telemetry/sessions.jsonl` in the cache dir. The line holds the report's summary and timing breakdown, and the machine's load average, CPUs and index size at that moment. The root is stored as a hash. The runtime dir a CI job deletes on exit is where the report reads its events; this line is what survives on the machine. `KACHE_RECORD_SESSIONS=1` or `[cache] record_sessions = true` makes every report record the same way without the flag, and appends each GC run to `telemetry/gc-runs.jsonl`; it is off by default. With neither, the command writes nothing.
## `kache telemetry`
```bash
kache telemetry write ./cache-otlp
kache telemetry write ./cache-otlp --scenario bench-firefox --phase warm
```
Writes `metrics.otlp.json` and `schema_version` for Kartero. Metric names are `kache.cache.*` / `kache.prefetch.*` (scope `kache.cache`), never `kache.bench.*`. `--scenario` must match `kache.bench.project`. `--phase` is `cold`, `warm`, or `pull` so dumps from the same scenario do not collide. The rustc wrapper does not write these files.
The payload also carries this host's shared cache: registered blob bytes (`kache.cache.store.physical_size`), index and WAL size, and a rowid high-water mark per table (`kache.cache.index.*`; it is not a row count). `kache.cache.store.size` counts logical entry bytes; GC compares the physical blob figure with `kache.cache.store.max`. Last-run GC gauges include duration, pinned and unreclaimable entries, bytes held by target directories, failed evictions, stale WAL snapshot failures, and recently accessed entries skipped before reading their metadata. The read-only index probe has a 25 ms lock timeout; unavailable figures are omitted.
## `kache why-miss`
```bash
kache why-miss serde
kache --json why-miss serde
```
<video src="https://raw.githubusercontent.com/kunobi-ninja/kache/main/assets/why-miss.webm" aria-label="one source edit, one recompile, and kache why-miss naming the key that changed" autoPlay loop muted playsInline controls />
Explains the latest recorded miss using the event log and stored entries. Store failures and rejected entries take precedence over key comparisons. Repeated misses under the same key in older logs retain an unknown cause when those logs cannot explain what happened.
Terminal and JSON output share the diagnosis and dependency analysis. For a recorded miss, JSON includes `dependency_chain` with changed dependencies, endpoints, and any traversal limit. An unresolved endpoint is not a confirmed cause. `dependency_recording_missing` indicates when enabling `[cache] explain_miss` and rebuilding can provide the missing dependency evidence.
The first build in a second clone or worktree has no earlier build of the crate in its own tree. In that case `why-miss` compares the miss with the most recent build of the same compilation unit in another build tree, usually the other checkout. The output names that tree and gives one of these verdicts:
- The crate's own input groups differ.
- Only its dependencies differ.
- Both trees computed the same key. No key input differs, so the key is not why the crate missed.
`why-miss` then walks the dependency cascade through the builds the other tree used. It names the crate that changed and shows the crates above it on the `via:` path. A root whose key matches the other tree while its artifact differs is reported as not reproducible across build trees (JSON kind `path_only`). JSON carries the comparison in `checkout_comparison` and the other tree in `dependency_chain.baseline_root`.
Build trees are matched by Cargo's unit id. It is the same for a workspace member in every checkout of a project, and for a registry crate built the same way in unrelated projects. Kache records it only with `[cache] explain_miss` on, so both builds need it. A build of the crate in the same tree is always preferred.
> A crate that depends on a path crate outside the workspace root, or is built with `RUSTFLAGS` that contain the checkout path, gets a different unit id in each checkout. It has no comparison.
## `kache report`
```bash
kache report --format text --since 24h
kache report --format markdown --root "$PWD"
kache report --last-build --root "$PWD" --format markdown
kache report --format json --output report.json
kache report --format trace --output trace.json
```
| Flag | Default | Values or purpose |
| --- | --- | --- |
| `--format` | `text` | `json`, `trace`, `perfetto`, `chrome-trace`, `markdown`, `github`, `text` |
| `--since` | `24h` | Event window; same forms as `kache stats` (`15m`, `2h`, `7d`, bare hours) |
| `--last-build` | off | Latest recorded activity session; conflicts with `--since` |
| `--root` | all roots | Include events from one build tree |
| `--output`, `-o` | stdout | Write to a file |
| `--top` | `10` | Number of ranked entries |
`trace`, `perfetto`, and `chrome-trace` select the same trace representation. The JSON contract is documented by [`report.schema.json`](https://github.com/kunobi-ninja/kache/blob/main/docs/commands/report.schema.json).
`--last-build` selects by the latest compiler event timestamp across the retained log, including events older than 24 hours. It includes only that root and session ID. For local or older events without session IDs, it infers a session from overlapping compiler activity and gaps shorter than five minutes, stopping at an event with a recorded ID. The report states which rule it used. `--root` limits the search to that tree and its descendants; the selected session still belongs to one exact root.
A session can span concurrent or nearby Cargo commands. This flag does not identify one Cargo invocation. A Cargo command that does no compiler work produces no new events. Compiles still running have not been logged yet. Log rotation can remove the start of a session. An empty selection is an error.
The session report includes cache hits, misses, bypass reasons, restored bytes and the observed compiler wall span. Compile work avoided is an aggregate estimate, not elapsed build time saved. Remote transfer totals and GC are omitted because those logs cannot identify the selected session. Store inventory and deduplication totals still describe the whole cache.
JSON exposes the selection in `meta.session` (`root`, optional `session_id`, `inferred`, `inactivity_secs`); trace output carries it as `session`. In session reports, `since_secs` and `since_hours` describe the lookback to the first retained compiler start; `meta.session` defines the selection.
Every report format totals the wrapper's phases per crate: startup, key computation, lookup, scheduler wait, restore or store, and the unattributed remainder. Startup runs from process start to wrapper entry. Key computation splits out the rustc dep-info pre-pass by time and run count. Scheduler wait is the flight join plus permit acquisition. Unattributed is wrapper overhead minus every measured phase, where overhead excludes the compile on a compiled crate and is the whole wrapper time on a hit. Events written by wrappers before schema 17 report zero startup, dep-info and wait time.
In the trace output each crate slice carries these numbers in its `args`. It nests one `X` slice per phase on the same lane, in this order: `startup`, `key` (with `dep-info` as its child), `lookup`, `wait`, `compile` on compiled crates, then `restore` or `store`. Phase offsets accumulate from the crate slice's start in that order; the wrapper records durations, not start times, so a phase sits where it lands in the sum. The gap after the last phase is the unattributed remainder. Durations are whole milliseconds, so a phase under 1 ms has no slice.
## `kache telemetry push`
After a build, inspect its timeline or send it to the configured service:
```bash
kache telemetry push --dry-run
kache telemetry push --all --label prefetch=on
kache telemetry push --session SESSION_ID --label prefetch=off
```
The default selects the latest session. `--all` selects every retained session;
`--session` selects one ID. `--label KEY=VALUE` is repeatable. Configure the
service through `KACHE_PLANNER_ENDPOINT` and its credentials. Upload failures
print a warning without failing a completed build.
Timeline schema 5 records the session, plan, candidate source, and zero-based
rank for ordinary prefetch downloads. Transfers retain `completed`,
`not_found`, `error`, and `import_error` outcomes. The origin stays with a
download even if another build starts before it finishes. A local-hit unit
and its prefetched transfer can therefore be joined by cache key inside the
correct session.
Each unit also carries `demands`, one observation per cache key looked up by
the wrapper. `first_demand_at_ms` is the first lookup's Unix timestamp in
milliseconds, including local hits. `remote_wait_ms` sums wall time blocked on
the existing daemon remote-check calls, including failed calls, IPC and daemon
admission. It does not estimate time saved. A stale predicted key and its
replacement retain separate observations. Recording adds no network request.
An orderly daemon shutdown drains prefetch tasks, then writes the active
session's local summary. A timeout aborts remaining tasks and sets `incomplete: true`;
missing transfer outcomes then remain unknown. The summary records `shutdown`
or `shutdown_timeout` as its closure reason. Shutdown does not publish an
identity manifest. Summaries written after inactivity or supersession are
snapshots, and pending tasks or log writes mark them incomplete. Complete
precision figures need a summary from an orderly shutdown, after all producers
drain.
`kache stats` marks incomplete summaries as partial totals. Forced termination
can prevent the final write.
Upgrade the service before sending schema 5 records. The updated service also
accepts schemas 1 through 4. Wrapper events before schema 20 have no demand
observations. Current demand transfers and older logs still require a unique
key or time-window match; ambiguous transfers remain unattributed.
Prefetch records each ordinary entry GET, catalog GET, pack GET and LIST once.
Background index and explicit warm-all LISTs retain the origin captured before
their async work. A completed LIST stays completed if later local index work
is cancelled; its result count and completion time describe the backend call.
`accounting.operation` distinguishes GET from LIST; `request_count` counts
backend invocations, excluding SDK retries and LIST pages. `requests_complete`
states whether that count is known. LIST records include `list_result_count`
when successful; response bytes remain unknown.
For GETs, `compressed_bytes` counts the received object body, including pack
headers and catalog metadata. `accounting.bytes_complete: false` means partial
received bytes are unknown, so a complete GET-body precision ratio cannot be
computed. A failed validation or import still retains a fully received body.
Cancellation receipts preserve known bytes and use the `cancelled` outcome.
Work cancelled before a GET starts records zero backend attempts. A 404 records
one attempt and known zero body bytes. Transport failures and interrupted bodies keep bytes unknown;
fully received bodies remain known even if decoding or import later fails.
`skipped` means another download or local entry made this candidate unnecessary;
it made no backend GET and is neutral in reports.
A bounded logging queue marks the final summary incomplete if records overflow
or cannot be written; such a run cannot establish full accounting coverage.
Pack `accounting.entries` contains each validated frame's cache key, compressed
payload bytes, immutable origin and outcome. `finished_at_ms` is the successful
local import time; zero means no import. Entries imported beyond the candidate
list have no candidate rank. Entry bytes are part of the physical pack body:
do not add them to total downloaded bytes. Useful bytes require an actual
wrapper hit in the same session and entry completion before first demand.
LIST response sizes are excluded from this GET-body measure.
For prefetch comparisons, keep cold builds and remote contents matched, label
the control run, and measure the whole CI job. Timeline compiler timings alone
do not include setup or an explicit warm step.
## `kache config`
```bash
kache config
```
This command can open even when the current config file is invalid. See [Configuration](/docs/getting-started/configuration).
## `kache login`
```bash
kache login
kache login --device
kache login --retrust
```
Logs in to the configured planner with your Kunobi account, for a planner that requires authentication. It reads the planner's issuer and client id from `/.well-known/kunobi-auth`, trusts that planner, and stores a session that the daemon refreshes. `--device` prints a URL and a code for a machine without a browser. `--retrust` accepts a planner whose login issuer changed since it was first trusted. See [Planner authentication](/docs/getting-started/configuration#planner-authentication).
## `kache logout`
```bash
kache logout
```
Forgets the stored planner session and asks the identity provider to revoke it.
## `kache install-shims`
```bash
kache install-shims
kache install-shims --from-path
kache install-shims --force ~/.local/lib/kache/shims
```
Unix only. Creates `cc`, `c++`, `gcc`, `g++`, `clang`, and `clang++` symlinks to Kache. With no directory argument the farm is `~/.local/lib/kache/shims`. `--from-path` also wraps compiler names already on `PATH` (`gcc-13`, target-prefixed drivers). Put the directory before the real compilers in `PATH`. If the directory then holds only Kache links, `install-shims` also writes a `.kache-shims` marker. When Kache looks for the real compiler, it skips everything in a marked directory. A directory that also holds other files, such as a real compiler, is left unmarked.
The links point at a path that survives upgrades when one reaches the running binary: Homebrew's `opt` link, a Nix profile, mise's `latest` alias, or a `kache` on `PATH` outside version directories. Without `--force`, existing entries are kept, except in a directory Kache created, marked, or filled only with Kache links. There a rerun replaces Kache links whose target is missing or not executable, and moves Kache links into a version directory to the upgrade-safe path. Other links are kept. See [C and C++](/docs/getting-started/c-cpp#upgrades).
## `kache test-runner`
```toml title=".cargo/config.toml"
[target.'cfg(all())']
runner = ["kache", "test-runner"]
```
Cargo runs each test binary as `kache test-runner <binary> <args>`. The binary waits for slots in the scheduler's pool, then runs with its arguments, stdin, exit code and signals unchanged. Cargo is the caller, so the command is not listed in `kache --help`. See [Test runner](/docs/getting-started/configuration#test-runner).
## `kache completions`
```bash
kache completions zsh > ~/.zfunc/_kache
kache completions bash > ~/.local/share/bash-completion/completions/kache
kache completions fish > ~/.config/fish/completions/kache.fish
```
Supported shells are Bash, Elvish, Fish, PowerShell, and Zsh.