Kache resolves settings in this order:
- environment variable
- config file
- 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:
kache config
Or edit TOML directly. Kache chooses the first file in this order:
KACHE_CONFIG- the nearest
.kache.toml, walking up from the current directory $XDG_CONFIG_HOME/kache/config.tomlor~/.config/kache/config.toml
The editor covers common fields and preserves advanced tables it does not expose.
[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:
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:
[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:
[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:
[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:
[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:
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:
[[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 or 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:
[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) fromkache-scenariokache.cache.*— cache counters (uploads, prefetch, store size) fromkache telemetry write
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:
[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.