kache 0.20.0

Zero-copy, content-addressed build cache for Rust, C/C++ and more, with S3 and shared-filesystem remotes.
---
title: Configuration
description: Configure Kache with TOML files and environment variables
---

Kache resolves settings in this order:

1. environment variable
2. config file
3. built-in default

`KACHE_DISABLED`, `KACHE_CONFIG`, `KACHE_SOCKET_PATH`, logging controls, progress controls, and credentials are operational settings. They do not all have TOML equivalents.

## Config file

Open the editor:

```bash
kache config
```

Or edit TOML directly. Kache chooses the first file in this order:

1. `KACHE_CONFIG`
2. the nearest `.kache.toml`, walking up from the current directory
3. `$XDG_CONFIG_HOME/kache/config.toml` or `~/.config/kache/config.toml`

The editor covers common fields and preserves advanced tables it does not expose.

```toml title="~/.config/kache/config.toml"
[cache]
cache_executables = true

[cache.remote]
type = "s3"
bucket = "my-build-cache"
region = "us-east-1"
```

Use `type = "filesystem"` with `path = "/mnt/kache"` for a shared filesystem remote. Only `s3` and `filesystem` are compiled remote backends.

## Core settings

| Environment | TOML key | Default | Purpose |
| --- | --- | --- | --- |
| `KACHE_CACHE_DIR` | `cache.local_store` | OS cache directory | Persistent store |
| `KACHE_RUNTIME_DIR` | `cache.runtime_dir` | local store | Socket, locks, logs, state, and session markers |
| none | `cache.volumes` | unset | Volume-local store shards, e.g. `[cache.volumes] "D:" = "D:/kache-store"`. Builds on a mapped volume share inodes there instead of copying across mounts. Unmapped volumes use the main store. |
| `KACHE_SOCKET_PATH` | none | `<runtime_dir>/daemon.sock` | Absolute daemon endpoint override |
| `KACHE_MAX_SIZE` | `cache.local_max_size` | 5% of the cache disk, floored at 5GiB and capped at 100GiB | Registered blob bytes allowed before GC. An explicit size always wins. `none` is not allowed. |
| `KACHE_AUTO_GC` | `cache.auto_gc` | `true` | Spawn throttled background GC under size pressure |
| `KACHE_GC_MAX_AGE_HOURS` | `cache.gc_max_age_hours` | `0` | Automatic age retention; `0` disables it |
| `KACHE_GC_EVICT_SHARED` | `cache.gc_evict_shared` | `false` | Restore namespace-first GC for entries retained by build outputs |
| `KACHE_MIN_STORE_COMPILE_MS` | `cache.min_store_compile_ms` | `0` | Skip local retention for faster compiles; `0` stores all eligible results |
| `KACHE_COMPRESSION_LEVEL` | `cache.compression_level` | `3` | Zstd level, clamped to 1 through 22 |
| none | `cache.event_log_max_size` | `10MiB` | Rotate the event log after this size |
| none | `cache.event_log_keep_lines` | `1000` | Lines retained when the event log rotates |
| `KACHE_DISABLED` | none | `false` | Pass all compiler work through when set to `1` or `true` |
| `KACHE_LOCAL_ONLY` | `cache.local_only` | `false` | Ignore all remote and planner configuration while keeping local caching |
| `KACHE_REMOTE_READONLY` | `cache.remote_readonly` | `false` | Allow remote reads but suppress remote writes. Untrusted CI (pull requests, tags, unprotected branches) also forces this on; `=0` does not turn that off. |
| `KACHE_SCHEDULER` | `cache.scheduler` | `true` | Pace real compiler processes across concurrent builds after a miss. Hits and passthroughs never wait. `0` or `false` turns it off. |

On macOS the default store is `~/Library/Caches/kache`; on Linux it is `$XDG_CACHE_HOME/kache` or `~/.cache/kache`; on Windows it is under `%LOCALAPPDATA%`.

## GC and shared build outputs

`cache.local_max_size` measures blob bytes registered in Kache's store. A Cargo target directory can hardlink or block-clone those blobs. In that case the store name and the build output refer to the same disk blocks.

By default, GC keeps an entry when removing its last store name would reclaim zero bytes. This preserves a usable cache hit. It also means the registered store size can remain above `local_max_size` until the target output is removed. `kache gc` reports these entries as unreclaimable and separates store bytes removed from disk bytes reclaimed.

To reclaim the blocks, remove the retaining build output. For known stale targets, preview Kache's machine-local registry before deleting anything:

```bash
kache clean --tracked --stale 14d --dry-run
kache clean --tracked --stale 14d --yes
```

If enforcing the store namespace limit matters more than retaining hits, restore the older policy:

```toml title="~/.config/kache/config.toml"
[cache]
gc_evict_shared = true
```

The equivalent environment setting is `KACHE_GC_EVICT_SHARED=1`. With this option, GC can drop an entry even though target files still hold all its blocks. The registered store size falls, but disk usage does not. A later build must compile or download that entry again. Explicit `purge` and repair operations remain unconditional regardless of this setting.

## Compiler policy

| Environment | TOML key | Default | Purpose |
| --- | --- | --- | --- |
| `KACHE_CACHE_EXECUTABLES` | `cache.cache_executables` | Linux/macOS: `true`; Windows: `false` | Cache eligible Rust bins and test executables |
| `KACHE_CLEAN_INCREMENTAL` | `cache.clean_incremental` | `true` | Remove tracked incremental directories during cleanup |
| `KACHE_ADAPTIVE_INCREMENTAL` | `cache.adaptive_incremental` | `true` | Learn rapidly changing Cargo units and give them isolated incremental state |
| `KACHE_PRESERVE_INCREMENTAL` | `cache.preserve_incremental` | `false` | Force eligible Cargo units onto isolated incremental state |
| `KACHE_INCREMENTAL_CRATES` | `cache.incremental_crates` | `[]` | Force listed rustc crate names onto that incremental path |
| `KACHE_MODIFIED_INPUT_GUARD` | `cache.modified_input_guard` | `false` | Do not store a result when a keyed input changed at or after build start |
| `KACHE_INPUT_PREDICTIONS` | `cache.input_predictions` | `false` | Remember the source closure each crate's dep-info pass discovers |
| `KACHE_FALLBACK` | `cache.fallback` | none | Wrapper used for ordinary Kache passthroughs |
| none | `cache.exclude` | `[]` | Source-path globs that bypass Kache |
| none | `cache.bypass_crates` | `[]` | Exact rustc crate names that bypass Kache |
| none | `cache.bypass_argv` | `[]` | Argument substrings that bypass Kache |
| none | `cache.bypass_env` | `[]` | `NAME` or `NAME=VALUE` rules that bypass Kache |
| `KACHE_CC_EXTRA_ALLOWLIST_FLAGS` | `cc.extra_allowlist_flags` | `[]` | Exact C/C++ flag spellings accepted in addition to the built-in model |

Bypass rules can only reduce caching:

```toml title=".kache.toml"
[cache]
exclude = ["vendor/problematic/**"]
bypass_crates = ["generated_bindings"]
bypass_argv = ["--cfg", "uncacheable_mode"]
bypass_env = ["SPECIAL_BUILD=1"]
```

## Cache-key controls

| Environment | TOML key | Default | Purpose |
| --- | --- | --- | --- |
| `KACHE_KEY_SALT` | `cache.key_salt` | none | Opaque value folded into every key |
| `KACHE_BASE_DIR` | none | none | One extra path prefix normalized as `<BASE_DIR>` |
| none | `paths.base_dirs` | `[]` | Additional absolute prefixes with distinct stable sentinels |
| `KACHE_PATH_ONLY_ENV_VARS` | `cache.path_only_env_vars` | `[]` | Path locator variables eligible for safe normalization |
| `KACHE_KEY_ENV_VARS` | `cache.key_env_vars` | `[]` | Environment read by proc macros but not reported by rustc |
| `KACHE_VERIFY_RESTORES` | none | `off` | Re-hash `sampled` or `always` before restoring |
| `KACHE_VERIFY` | none | `off` | Recompile each rustc cache hit and compare it to the restored files |

`KACHE_VERIFY` is qualification for cache-key changes. Set it to `1` or `true` to recompile every rustc cache hit into a staging directory and diff those artifacts against the files just restored. Leave it unset (or `0` / `false`) in ordinary builds: each hit pays a full compile. There is no `kache build` command for this.

Path and debug-info bytes that differ only as embedded absolute paths are reported as `path-debug` and do not fail the build. Remaining byte differences are `content`. In both cases the restored files stay in place so the build continues; the wrapper records the class on the hit event as `verify_compare` (and in tracing when logging is on).

`KACHE_VERIFY_RESTORES` still only re-hashes stored blobs before restore. It does not recompile.

Use a key salt when an unobserved toolchain component changes output:

```toml
[cache]
key_salt = "sysroot-2026-08"
```

Keep `paths.base_dirs` narrow. Normalizing unrelated roots to stable sentinels can make different inputs appear equal.

`key_env_vars` accepts exact names and trailing-star prefix patterns:

```toml
[cache]
key_env_vars = ["BOLTFFI_*"]
```

Values are hashed exactly and are not written to event logs. A changing value such as a job ID will destroy hit rate.

## Hidden compile-time inputs

Some macros read files that rustc does not report. Put `kache.toml` without a leading dot next to the crate manifest:

```toml title="crates/db/kache.toml"
extra_inputs = [
  ".sqlx/**/*.json",
  "migrations/**/*.sql",
]
```

Matches are relative to that crate. Kache folds their paths and contents into its key and adds bounded watches to Cargo dep-info. Invalid or unreadable declarations fail closed.

For a workspace-root input used by a provider package and its direct consumers:

```toml title=".kache.toml"
[[workspace.extra_inputs]]
crates = ["query-macros"]
inputs = [".sqlx/**/*.json"]
propagate_to_dependents = true
```

Workspace rules are accepted only from the `.kache.toml` beside the active Cargo workspace manifest. After adding a declaration to a target Cargo already considers fresh, rebuild that package once.

## Remote settings

| Environment | TOML key | Default | Purpose |
| --- | --- | --- | --- |
| none | `cache.remote.type` | inferred as S3 for legacy config | `s3` or `filesystem` |
| `KACHE_S3_BUCKET` | `cache.remote.bucket` | none | S3 bucket |
| `KACHE_S3_ENDPOINT` | `cache.remote.endpoint` | AWS | Custom S3-compatible endpoint |
| `KACHE_S3_REGION` | `cache.remote.region` | `us-east-1` | Region |
| `KACHE_S3_PREFIX` | `cache.remote.prefix` | `artifacts` | Object prefix |
| `KACHE_S3_PROFILE` | `cache.remote.profile` | credential-chain default | AWS profile |
| `KACHE_S3_USER_AGENT` | `cache.remote.user_agent` | none | Custom HTTP User-Agent |
| none | `cache.remote.path` | none | Filesystem remote root |
| none | `cache.remote.atomic_write_dir` | `<path>/.kache-tmp` | Same-filesystem staging directory |
| `KACHE_S3_CONCURRENCY` | `cache.s3_concurrency` | `16` | Concurrent operations for either backend |
| `KACHE_S3_POOL_IDLE_SECS` | `cache.s3_pool_idle_secs` | `300` | S3 connection pool idle time |
| `KACHE_REMOTE_RESTORE_TIMEOUT_SECS` | `cache.remote_restore_timeout_secs` | `300` | Daemon operation deadline; wrapper demand remains capped at three seconds |
| `KACHE_REMOTE_NEGATIVE_TTL_SECS` | `cache.remote_negative_ttl_secs` | `60` | Cache definitive remote 404 results |

Credentials resolve from explicit `KACHE_S3_ACCESS_KEY` and `KACHE_S3_SECRET_KEY`, standard AWS variables, the selected AWS profile, web identity, container credentials, then instance credentials.

See [S3 setup](/docs/remote-cache/s3-setup) or [filesystem setup](/docs/remote-cache/filesystem-setup).

## Prefetch and planner

| Environment | TOML key | Default | Purpose |
| --- | --- | --- | --- |
| `KACHE_PREFETCH_ENABLED` | `cache.prefetch_enabled` | `true` | Enable speculative manifest and planner prefetch |
| `KACHE_REMOTE_KEY_CACHE_REFRESH_SECS` | `cache.remote_key_cache_refresh_secs` | `60` | Refresh the fallback planner's remote key index; `0` means initial load only |
| `KACHE_PREFETCH_MAX_KEYS` | `cache.prefetch_max_keys` | `2000` | Entries allowed per plan; `0` is unlimited |
| `KACHE_PREFETCH_MAX_BYTES` | `cache.prefetch_max_bytes` | `2GiB` | Compressed bytes allowed per plan; in-flight downloads may finish |
| `KACHE_PREFETCH_DEADLINE_SECS` | `cache.prefetch_deadline_secs` | `300` | Time during which a plan may start downloads; `0` disables the deadline |
| `KACHE_PLANNER_ENDPOINT` | `cache.planner.endpoint` | none | Planner service URL |
| `KACHE_PLANNER_TIMEOUT_MS` | `cache.planner.timeout_ms` | `750` | Planner request timeout |
| `KACHE_PLANNER_TOKEN` | `cache.planner.token` | none | Bearer token |
| `KACHE_NAMESPACE` | none | none | Shard namespace used by manifest upload and prefetch |

On a long-lived runner with a warm local store, speculative prefetch may be unnecessary:

```toml
[cache]
prefetch_enabled = false
```

Exact remote lookup and background upload continue to work.

## Telemetry

Kache does not push metrics to a collector. It writes OTLP JSON files that Kartero imports from CI artifacts, the same way nightly benches already work.

Two signals, never mixed:

- `kache.bench.*` — one-shot bench gauges (duration, speedup, verdict) from `kache-scenario`
- `kache.cache.*` — cache counters (uploads, prefetch, store size) from `kache telemetry write`

```bash
kache telemetry write ./cache-otlp --scenario bench-firefox --phase warm
```

That writes `metrics.otlp.json` and `schema_version` in the directory. `--scenario` must match `kache.bench.project`. `--phase` distinguishes cold from warm: the bench stops the daemon between phases, so counters are per daemon lifetime. The rustc wrapper does not write these files. The planner service still exposes Prometheus `/metrics` for cluster scrape.

## Daemon, events, and diagnostics

| Environment | TOML key | Default | Purpose |
| --- | --- | --- | --- |
| `KACHE_DAEMON_IDLE_TIMEOUT` | `cache.daemon_idle_timeout_secs` | `0` | Stop after idle seconds; `0` keeps it running |
| `KACHE_LOCAL_HIT_DAEMON` | `cache.local_hit_daemon` | `false` | Experimental daemon-assisted local lookup with local fallback |
| `KACHE_HEARTBEAT_SECS` | `cache.heartbeat_secs` | `30` | Event heartbeat cadence for long miss compiles; `0` disables it |
| `KACHE_EXPLAIN_MISS` | `cache.explain_miss` | `false` | Record changed key groups on repeat misses |
| `KACHE_STORAGE_LAYOUT_ADVICE` | `cache.storage_layout_advice` | `true` | Warn when storage layout forces copies |
| `KACHE_WINDOWS_HARDLINK` | `cache.windows_hardlink` | `false` | Unsafe opt-in hardlink restores on non-CoW Windows volumes |
| `KACHE_PROGRESS` | none | off | `hits` or `verbose` compiler progress |
| `KACHE_LOG` | none | CLI: `kache=warn`; wrapper: off | Stderr tracing filter |
| `KACHE_LOG_FILE` | none | CLI/daemon: `kache=info`; wrapper: off | File tracing filter and wrapper opt-in |
| `KACHE_EVENT_ROOT` | none | detected | Root attached to events for report filtering |
| `KACHE_PAGER` | none | platform pager | Pager command for `kache list` |

## Pin file-backed settings

Set `cache.ignore_env` in a controlled project config to ignore environment overrides for settings that also exist in TOML:

```toml
[cache]
ignore_env = true
```

It does not suppress `KACHE_DISABLED`, `KACHE_CONFIG`, `KACHE_SOCKET_PATH`, credentials, logging, progress, or other environment-only controls. Kache warns when it ignores a set variable.

The daemon reads configuration at startup and watches the active config file. File changes trigger a graceful restart. Environment changes require starting a new daemon from that environment; an installed service uses its service definition, not your current shell.