---
title: Configuration
description: Config file, environment variables, and the TUI editor.
---

# Configuration

kache reads configuration from three places, in order of priority:

1. **Environment variables** — always win, useful for CI overrides
2. **Config file** — selected from the config file priority below
3. **Defaults** — sensible values that work without any configuration

## Config file

The config file is TOML. You can edit it directly or use the TUI editor:

```sh
kache config
```

The TUI editor surfaces the common fields with their current values, marks which ones are coming from env vars (those are read-only and the cursor skips them), and lets you toggle or edit the rest interactively. Navigate with the arrows or `j`/`k`, jump between sections with Tab/Shift-Tab, Enter edits a field, Space toggles a boolean, `s` (or Ctrl-S) saves, and `q`/Esc quits with an unsaved-changes prompt. Some advanced sections — `[cache.planner]`, `[cc]`, and `cache.path_only_env_vars` — have no form fields and are preserved verbatim on save.

Config file priority:

1. `KACHE_CONFIG`, when set
2. The nearest project-local `.kache.toml`, walking up from the current directory
3. User config at `~/.config/kache/config.toml`, respecting `XDG_CONFIG_HOME`

A minimal config that enables a remote cache looks like this:

```toml title="~/.config/kache/config.toml"
[cache.remote]
type = "s3"
bucket = "my-build-cache"
endpoint = "https://s3.example.com"   # omit for AWS S3
profile = "my-aws-profile"            # omit to use the default credential chain
```

## All settings

| Environment variable | Config key | Default | Description |
|---|---|---|---|
| `KACHE_CACHE_DIR` | `cache.local_store` | `~/Library/Caches/kache` on macOS, `~/.cache/kache` on Linux | Local cache directory |
| `KACHE_MAX_SIZE` | `cache.local_max_size` | `50GiB` | Maximum local store size |
| `KACHE_CONFIG` | — | — | Explicit config file path; overrides the project-local `.kache.toml` / XDG resolution |
| `KACHE_S3_BUCKET` | `cache.remote.bucket` | — | S3 bucket name |
| `KACHE_S3_ENDPOINT` | `cache.remote.endpoint` | — | S3 endpoint URL (required for Ceph, MinIO, R2) |
| `KACHE_S3_REGION` | `cache.remote.region` | `us-east-1` | AWS region |
| `KACHE_S3_PREFIX` | `cache.remote.prefix` | `artifacts` | Key prefix inside the bucket |
| `KACHE_S3_PROFILE` | `cache.remote.profile` | — | AWS credentials profile |
| `KACHE_S3_ACCESS_KEY` | — | — | Explicit S3 access key |
| `KACHE_S3_SECRET_KEY` | — | — | Explicit S3 secret key |
| `KACHE_CACHE_EXECUTABLES` | `cache.cache_executables` | `false` | Also cache user-facing executables (`bin` crates and `--test` binaries). dylib/cdylib/proc-macro are always cached and are unaffected by this flag |
| `KACHE_CLEAN_INCREMENTAL` | `cache.clean_incremental` | `true` | Auto-clean tracked incremental dirs during GC; active builds also remove the current crate's incremental dir eagerly. Env is on unless the value is exactly `0` or `false` (case-insensitive) |
| — | `cache.exclude` | `[]` | Source-path glob patterns that bypass kache and compile normally without lookup, store, or upload |
| `KACHE_COMPRESSION_LEVEL` | `cache.compression_level` | `3` | Zstd compression level (1–22) |
| `KACHE_S3_CONCURRENCY` | `cache.s3_concurrency` | `16` | Max concurrent S3 operations |
| `KACHE_S3_POOL_IDLE_SECS` | `cache.s3_pool_idle_secs` | `300` | How long an idle S3 connection is kept in the HTTP pool. Higher values reuse warm TLS sessions across build phases; lower this if you sit behind a load balancer that drops idle connections aggressively |
| `KACHE_DAEMON_IDLE_TIMEOUT` | `cache.daemon_idle_timeout_secs` | `600` | Idle daemon shutdown timeout in seconds (`0` disables auto-shutdown) |
| `KACHE_KEY_SALT` | `cache.key_salt` | — | Opaque string folded into every cache key. Change it to force a cold cache on a toolchain change kache cannot otherwise see (see [Cache-key salt](#cache-key-salt)) |
| `KACHE_CC_EXTRA_ALLOWLIST_FLAGS` | `cc.extra_allowlist_flags` | `[]` | C/C++ flags to opt into caching that kache's built-in allow-list doesn't yet model (see [Extra cc allowlist flags](#extra-cc-allowlist-flags)). Env value is whitespace-separated |
| `KACHE_DISABLED` | — | `false` | Disable caching entirely (pass-through to rustc). Env-only, no config key: `1` or `true` (case-insensitive) disables; any other value — including `0`, `false`, or unset — leaves caching on |
| `KACHE_LOCAL_ONLY` | `cache.local_only` | `false` | Strict local-only mode: ignore **all** remote and planner config/env (no S3 bucket, no planner endpoint, no egress) for a guaranteed-hermetic build. Local caching stays fully on — unlike `KACHE_DISABLED`. `1`/`true` enables; env wins over the file (an explicit `0` overrides `local_only = true`) |
| `KACHE_FALLBACK` | `cache.fallback` | — | Secondary compiler-wrapper to hand passed-through compiles to; kache runs `<fallback> <compiler> <args>` when it declines to cache. `off`/`none`/empty disables |
| `KACHE_PATH_ONLY_ENV_VARS` | `cache.path_only_env_vars` | `[]` | Extra env vars (besides `OUT_DIR`) whose values only locate an `include!`'d file, so kache normalizes their absolute path in the cache key. Env value is comma/whitespace-separated and replaces the file list (see [Path-only env vars](#path-only-env-vars)) |
| `KACHE_PLANNER_ENDPOINT` | `cache.planner.endpoint` | — | Prefetch-planner service URL; setting it enables the planner client. Empty/whitespace is treated as unset |
| `KACHE_PLANNER_TIMEOUT_MS` | `cache.planner.timeout_ms` | `750` | Planner request timeout in milliseconds |
| `KACHE_PLANNER_TOKEN` | `cache.planner.token` | — | Bearer credential sent with planner requests |
| `KACHE_LOG` | — | `kache=warn` * | Log level for stderr output |
| `KACHE_LOG_FILE` | — | `kache=info` * | Log level for the file log |
| `KACHE_PROGRESS` | — | (off) | Per-crate progress lines to stderr: `1`/`hits` prints cache hits only; `verbose`/`all` also prints dups and misses; unset or anything else stays silent |

\* In wrapper mode (`RUSTC_WRAPPER`, the common path) stderr logging defaults to **off** and the file log is **disabled** unless `KACHE_LOG_FILE` is set explicitly. The `kache=warn` / `kache=info` defaults apply to CLI and daemon invocations.

## Excluding Sources

Use `cache.exclude` when a source file should compile normally but never use kache:

```toml title=".kache.toml"
[cache]
exclude = [
  "crates/problematic-rust-crate/**",
  "vendor/problematic-c-lib/**",
  "$CARGO_HOME/registry/src/**/some-crate-*/**",
]
```

Patterns are globs matched against the compiler's primary source path. Relative patterns are matched relative to the current build directory and, when kache can infer it, the Cargo workspace root. Excluded invocations bypass local lookup, remote lookup, store, and upload.

Exclude patterns support `~`, `$VAR`, and `${VAR}` expansion. `$CARGO_HOME` falls back to Cargo's default `~/.cargo` when the environment variable is not set, so registry-source exclusions work on default Cargo installs.

## Cache-key salt

kache's cache key captures everything it can *observe* about a compile: the rustc version, the target, flags, source and dependency hashes, and the linker's `--version` banner. That set is complete for divergences a tool reports in its version output. It is **not** complete for toolchain changes that alter compiled output while leaving every banner unchanged — most commonly a `glibc`, `mold`, or linker bump, or a Nix store rebuild that swaps the ELF interpreter baked into a binary.

When that happens, the key does not move, so a stale artifact can be restored. On Nix in particular, a `nixpkgs` bump followed by `nix store gc` can leave a restored executable pointing at a garbage-collected interpreter — an error no `cargo clean` can fix, because the key never changed.

`key_salt` is an opaque string folded into every cache key. Set it to a value that *does* change when your toolchain changes — a hash of the toolchain closure, a store-path digest, a date, a CI build id — and that change re-keys the cache (a cold miss) instead of serving a stale hit:

```toml title=".kache.toml"
[cache]
key_salt = "nixpkgs-a1b2c3d-mold-2.40"
```

Or compute it from the toolchain itself:

```sh
export KACHE_KEY_SALT="$(nix eval --raw .#devShells.default.outPath | sha256sum | cut -c1-16)"
```

The salt is hashed raw; its meaning is entirely yours. An unset or empty value has **no effect** — keys are byte-identical to not setting it — so it is safe to leave off until you need it. A misconfigured salt can only cost hit rate (an extra miss), never cause a wrong artifact to be restored.

## Extra cache-key inputs

kache keys a crate on what the compiler reports — source files, `--extern` dependencies, flags. Some crates also read files **at compile time that the compiler never reports**, so kache can't see them change:

- sqlx's `query!` macro reads `.sqlx/query-*.json` (the offline query cache)
- migration macros read `migrations/`
- codegen / macros that `include!` data files by a path rustc doesn't surface

Edit one of those and the `.rs` files are unchanged, so the key doesn't move — and kache would restore the previously-compiled artifact (a stale hit). Declare those files so a change to them re-keys the crate.

Add a `kache.toml` next to the crate's `Cargo.toml`:

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

Globs are relative to the crate directory. A bare directory is fine — `.sqlx` and `.sqlx/` both mean "everything under it." The matched files' contents are folded into that crate's key.

A pattern **may** reach outside the crate with `..` or an absolute path when a build genuinely depends on a shared tree above the crates or a machine-specific file. That's allowed and stays fail-safe, but it makes the crate's key host- or layout-specific (kache logs a warning), so it no longer shares across machines or worktrees — prefer a co-located input where you can.

This is **opt-in and scoped per crate**: a crate with no `kache.toml` is unaffected, and one crate's `extra_inputs` never *implicitly* touch a sibling crate's key. It is also **union-only** — a misdeclared glob can only cause an extra cache *miss* (a rebuild), never restore a wrong artifact. Each file is folded as its crate-relative path plus content hash, so moving the worktree doesn't bust the cache, but swapping two matched files' contents (where order matters, like sqlx migrations) does re-key. The declared patterns are folded too, so editing `kache.toml` itself re-keys — even when it currently matches nothing.

<Callout type="warn">
  Keep patterns narrow. A glob that walks a huge tree (an absolute `/**`, or a
  `**/*` that accidentally spans `target/`) re-keys on every change and re-walks
  the tree on every compile — kache warns when a pattern matches the filesystem
  root or an unusually large number of files.
</Callout>

<Callout type="info">
  `kache.toml` (no leading dot) is **only** for `extra_inputs`. It is distinct
  from the project config `.kache.toml`; any other key in it is a loud error.
</Callout>

## Path-only env vars

By default kache normalizes the absolute path baked into `OUT_DIR` so the cache key stays portable across machines and worktrees. Some builds set other env vars that serve the same role — they only point at a generated file that a crate `include!`s, and their absolute value should not bust the key (e.g. Firefox's `BUILDCONFIG_RS` / `MOZ_TOPOBJDIR`).

`cache.path_only_env_vars` opts those vars into the same path-only treatment:

```toml title=".kache.toml"
[cache]
path_only_env_vars = ["BUILDCONFIG_RS", "MOZ_TOPOBJDIR"]
```

Or via the environment (comma- or whitespace-separated; replaces the file list entirely):

```sh
export KACHE_PATH_ONLY_ENV_VARS="BUILDCONFIG_RS MOZ_TOPOBJDIR"
```

This is an advanced opt-in: only list vars whose value is purely a path locator. An empty list means only `OUT_DIR` is normalized.

## Extra cc allowlist flags

kache caches a C/C++ compile only when it recognizes every flag on the command line. Its cc flag classifier is an **allow-list**: each flag is one kache has reasoned about and knows how to key. Anything unrecognized is refused and the compile passes through uncached — the safe default, since a flag kache doesn't model could change the object file without changing the key.

**Supported compilers and dialects.** kache wraps the GNU-dialect drivers (`cc`, `c++`, `gcc`, `g++`, `clang`, `clang++`; POSIX) and the MSVC-dialect `clang-cl` (Windows, or `clang --driver-mode=cl`). The two dialects have separate allow-lists, so each flag is classified in the spelling its compiler actually uses — e.g. `-fno-rtti` (GNU) vs `-GR-` (clang-cl), `-std=c++20` vs `-std:c++20`. For clang-cl, debug info (`/Z7` / `-Z7` / `-g`) **is** cached (machine-local — clang-cl embeds CodeView paths in the `.obj`), while `-bigobj` and `-showIncludes` are still deliberately **refused** (passed through) for now; the common MSVC codegen set (`-EH*`, `-GR`/`-GS`, `-guard:`, `-std:`, `-fms-compatibility-version=`, `/O*`, `-Gy`/`-Gw`, `-MD`/`-MT`, …) is modeled. `extra_allowlist_flags` entries below are matched against command-line tokens verbatim, so list them in whichever dialect your compiler speaks (a `/`-spelled clang-cl flag included).

The cost is that a common-but-unlisted flag disables caching for that compile until kache ships a release adding it. `cc.extra_allowlist_flags` lets you opt such a flag in locally, ahead of official support:

```toml title=".kache.toml"
[cc]
extra_allowlist_flags = ["-ffunction-sections", "-fdata-sections", "-fno-rtti"]
```

Or via the environment (whitespace-separated; overrides the file):

```sh
export KACHE_CC_EXTRA_ALLOWLIST_FLAGS="-ffunction-sections -fdata-sections"
```

Semantics:

- **Exact match.** An entry matches a command-line token character-for-character — no prefixes or wildcards. List each value you use (e.g. `-march=armv8.2-a`, not `-march=`).
- **Hashed verbatim.** A matched flag that's actually present is folded into the cache key as its literal string, so a different flag (or value) always produces a different key — it can never miscache *by value*.
- **Add-only.** This can only make kache *stop refusing* a flag. It cannot override structural refusals — link mode, coverage instrumentation, multi-`-arch`, precompiled headers, modules, and the like still pass through.
- **Off by default.** An empty list has no effect; keys are byte-identical to not setting it.

<Callout type="warn">
Avoid host-dependent flags like `-march=native`. The string is identical on every machine but compiles to different objects per CPU, so hashing it verbatim collides across hosts — a cache hit on one machine can restore an object built for another. List explicit architectures instead.
</Callout>

To confirm it took effect, run with `KACHE_LOG=kache=debug`: the cc flag-classify summary gains a `user-allowed` count, and the flag no longer appears in any `unsupported flag(s): … — passthrough` line. At `kache=trace` each accepted flag logs as `user-allowed (config)` and each one folded into the key logs as `cc_extra_flag=`.

## Credential resolution order

Remote S3 credentials are resolved in this order:

1. `KACHE_S3_ACCESS_KEY` + `KACHE_S3_SECRET_KEY`
2. AWS profile from `cache.remote.profile` or `KACHE_S3_PROFILE`
3. AWS default chain, such as `AWS_ACCESS_KEY_ID`, `~/.aws/credentials [default]`, or IAM roles

For Ceph, MinIO, or R2, set `cache.remote.endpoint` or `KACHE_S3_ENDPOINT`.

```toml title="Ceph example"
[cache.remote]
type = "s3"
bucket = "build-cache"
endpoint = "https://s3.example.com"
profile = "ceph"
```

## Size values

Size fields (`KACHE_MAX_SIZE`, `cache.local_max_size`) accept human-friendly strings:

```
50GiB   10GB   512MiB   1024MB
```

## Log levels

`KACHE_LOG` follows the `tracing` subscriber syntax:

```sh
KACHE_LOG=kache=debug   # verbose, useful when diagnosing cache misses
KACHE_LOG=kache=info    # operational detail
KACHE_LOG=kache=warn    # default — only surface real problems
```

The file log is written to `~/Library/Logs/kache/kache.log` on macOS and `~/.cache/kache/kache.log` elsewhere. It rotates automatically when it exceeds 5 MB.

## Local store layout

```
<cache_dir>/               # Linux: ~/.cache/kache · macOS: ~/Library/Caches/kache · Windows: %LOCALAPPDATA%\kache
├── store/
│   ├── blobs/
│   │   └── ab/
│   │       └── abcdef...   # content-addressed blob (blake3), stored once
│   └── <cache_key>/
│       └── meta.json       # entry metadata; references its blobs by hash
├── index.db                # SQLite index (WAL mode) — sibling of store/, not inside it
├── events.jsonl            # build event log
├── transfers.jsonl         # transfer log
└── daemon.sock             # daemon IPC socket (a named pipe on Windows)
```

The whole cache directory is excluded from Time Machine and Spotlight on macOS automatically — on first start the daemon sets a `tmutil` exclusion and a `.metadata_never_index` sentinel.
