Skip to content
Getting Started

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:

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], [paths], cache.path_only_env_vars, and cache.key_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

kache currently compiles two remote types: s3 and filesystem.

[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
[cache.remote]
type = "filesystem"
path = "/mnt/shared-kache"
prefix = "artifacts"

atomic_write_dir is optional and defaults to /mnt/shared-kache/.kache-tmp. See Filesystem setup before overriding it.

Other OpenDAL services are not included in the kache binary. Existing S3 configuration names remain compatible: a legacy [cache.remote] table without type = "s3" and an environment-only setup rooted in KACHE_S3_BUCKET still select S3; the other KACHE_S3_* overrides remain available. See the S3 migration boundaries for AWS SDK-specific cases that need attention.

All settings

Environment variableConfig keyDefaultDescription
KACHE_CACHE_DIRcache.local_store~/Library/Caches/kache on macOS, ~/.cache/kache on LinuxLocal cache directory
KACHE_MAX_SIZEcache.local_max_size50GiBMaximum local store size
KACHE_AUTO_GCcache.auto_gctrueOpportunistic size-pressure GC: after storing a new entry the wrapper runs a cheap, throttled (5 min) store-size check and spawns a detached background kache gc when the store exceeds local_max_size by more than 10%. Keeps the size cap enforced even when no daemon is running (local-only builds). Set to 0/false to rely solely on daemon GC and manual kache gc
KACHE_CONFIGExplicit config file path; overrides the project-local .kache.toml / XDG resolution
KACHE_BASE_DIRPath prefix stripped from cache keys (collapsed to <BASE_DIR>) for checkout / container-mount paths the automatic sentinels don't catch — the analog of ccache's CCACHE_BASEDIR (see Cache key)
— (file-only)paths.base_dirs[]Extra absolute path prefixes for container mounts, Snap, Flatpak, AppImage, or custom roots; each maps to a distinct deterministic sentinel (see Extra path prefixes)
— (file-only)cache.remote.typeRemote type: s3 or filesystem. A legacy S3 table or KACHE_S3_BUCKET still selects S3 when this is omitted
— (file-only)cache.remote.pathShared root directory for a filesystem remote
— (file-only)cache.remote.atomic_write_dir<path>/.kache-tmpStaging directory for filesystem-remote atomic writes; must be on the same filesystem as path
KACHE_S3_BUCKETcache.remote.bucketS3 bucket name
KACHE_S3_ENDPOINTcache.remote.endpointS3 endpoint URL (required for Ceph, MinIO, R2)
KACHE_S3_REGIONcache.remote.regionus-east-1AWS region
KACHE_S3_PREFIXcache.remote.prefixartifactsObject or path prefix. The environment override is retained for S3; filesystem remotes configure it in the file
KACHE_S3_PROFILEcache.remote.profileAWS credentials profile
KACHE_S3_ACCESS_KEYExplicit S3 access key
KACHE_S3_SECRET_KEYExplicit S3 secret key
KACHE_CACHE_EXECUTABLEScache.cache_executablestrue on Linux, false on macOS/WindowsAlso cache user-facing executables (bin crates and --test binaries). dylib/cdylib/proc-macro are always cached and are unaffected by this flag. Defaults on for Linux only, where DWARF is embedded in the binary so a restored executable debugs like a fresh one; macOS (N_OSO records) and Windows (.pdb path) reference debug info outside the binary, so they stay off pending #319
KACHE_CLEAN_INCREMENTALcache.clean_incrementaltrueAuto-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)
KACHE_VERIFY_RESTORESoffRe-hash restored blobs before serving hits: off, sampled (~1/16 hits), or always. 1/true map to always
cache.exclude[]Source-path glob patterns that bypass kache and compile normally without lookup, store, or upload
KACHE_COMPRESSION_LEVELcache.compression_level3Zstd compression level (1–22)
KACHE_S3_CONCURRENCYcache.s3_concurrency16Max concurrent remote operations; the legacy S3 name applies to both remote types
KACHE_PREFETCH_ENABLEDcache.prefetch_enabledtrueEnable speculative manifest/advisory/fallback prefetch. Set to 0/false on long-lived warm runners to skip manifest warming, build-start planning, and the whole-remote key index. Exact-key remote checks and background uploads remain enabled. Environment changes require kache daemon restart; prefer the watched config file for persistent daemons
KACHE_REMOTE_KEY_CACHE_REFRESH_SECScache.remote_key_cache_refresh_secs60Remote key-index refresh interval used by speculative fallback planning. A negative index entry suppresses an exact remote HEAD only while the index is fresh, for at most min(refresh × 5, 300s). 0 performs one initial population, never treats negative entries as authoritative, and disables periodic refresh. Ignored when prefetch_enabled = false
KACHE_PREFETCH_MAX_KEYScache.prefetch_max_keys2000Max cache entries one prefetch plan may download; 0 = unlimited
KACHE_PREFETCH_MAX_BYTEScache.prefetch_max_bytes2GiBMax compressed bytes one prefetch plan may download; 0 = unlimited. Soft cap: downloads already in flight still finish, so overshoot is bounded by the prefetch concurrency
KACHE_PREFETCH_DEADLINE_SECScache.prefetch_deadline_secs300How long a prefetch plan may keep starting downloads; 0 = no deadline
KACHE_S3_POOL_IDLE_SECScache.s3_pool_idle_secs300How 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_TIMEOUTcache.daemon_idle_timeout_secs600Idle daemon shutdown timeout in seconds (0 disables auto-shutdown)
KACHE_KEY_SALTcache.key_saltOpaque 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)
KACHE_CC_EXTRA_ALLOWLIST_FLAGScc.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). Env value is whitespace-separated
KACHE_DISABLEDfalseDisable 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_ONLYcache.local_onlyfalseStrict local-only mode: ignore all remote and planner config/env (no configured remote, 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_REMOTE_READONLYcache.remote_readonlyfalseRead-only remote consumer mode: when enabled, kache performs remote cache reads/restores as normal, but suppresses all remote uploads (including the sync push phase and manifest saving). 1/true enables; env wins over the file (an explicit 0 overrides remote_readonly = true)
KACHE_WINDOWS_HARDLINKcache.windows_hardlinkfalseWindows only: restore cache hits on non-CoW volumes (NTFS) via hardlink instead of copy, deduplicating the working tree against the store. Opt in only if your build never deletes or rewrites a restored output in place — a mutation would corrupt the shared store blob. ReFS (Dev Drive) volumes always block-clone regardless of this flag
KACHE_STORAGE_LAYOUT_ADVICEcache.storage_layout_advicetrueSurface an advisory (deduplicated, at most once per 5-minute window) when a cache hit is restored by copy because the storage layout prevents zero-copy dedup: no copy-on-write on the volume, cache and build tree on different volumes, or an inconclusive capability probe. Set to 0/false when the layout is intentional (e.g. an NTFS-only machine that cannot host a ReFS Dev Drive); genuine clone faults are still reported
KACHE_HEARTBEAT_SECScache.heartbeat_secs30In-flight compile heartbeat cadence: while a cache-miss compile runs longer than one cadence, kache appends a structured heartbeat line to events.jsonl. With KACHE_PROGRESS=verbose/all, it also prints still compiling <crate> — 4m20s elapsed (typical: 7m51s, ETA 3m31s) to stderr. The typical/ETA figures come from the median of the crate's recent recorded compile times. 0 disables both sinks
KACHE_EXPLAIN_MISScache.explain_missfalseMiss diagnostics: on a cache miss for a crate that previously hit in the same build tree, print which key input group changed (key changed in: args, env_deps) and record it on the miss event. Costs one event-log read per miss, so enable it only while investigating unexpected misses
— (file-only)cache.ignore_envfalseMake the config file authoritative by ignoring KACHE_* env overrides for file-backed settings (see Pinning config against env)
KACHE_FALLBACKcache.fallbackSecondary 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_VARScache.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)
KACHE_KEY_ENV_VARScache.key_env_vars[]Env vars to fold into every cache key, for values a proc macro reads at expansion time that the compiler never reports. Exact names or a trailing-* prefix glob. Env value is comma/whitespace-separated and replaces the file list (see Env vars that steer expansion)
KACHE_PLANNER_ENDPOINTcache.planner.endpointPrefetch-planner service URL; setting it enables the planner client. Empty/whitespace is treated as unset
KACHE_PLANNER_TIMEOUT_MScache.planner.timeout_ms750Planner request timeout in milliseconds
KACHE_PLANNER_TOKENcache.planner.tokenBearer credential sent with planner requests
KACHE_NAMESPACEEnables content-addressed shard uploads/prefetch (requires a Cargo.lock); the kache save-manifest --namespace flag takes precedence. Unset uploads only the monolithic manifest
KACHE_EVENT_ROOTauto-detectedExplicit root path stamped on wrapper events for report filtering; useful for benchmark harnesses that share a cache/event log
KACHE_LOGkache=warn *Log level for stderr output
KACHE_LOG_FILEkache=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, misses, and in-flight heartbeats; 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.

Prefetch policy by runner lifetime

Speculative prefetch is designed to hide remote latency on a cold runner by downloading likely artifacts before rustc asks for their exact keys. It is optional: exact-key remote lookup and background uploads use separate daemon paths.

For a long-lived runner with a persistent local store, speculative prefetch can cost more than it saves. The local store is already warm, while fallback planning may have to index a large shared remote and choose among many variants of the same crate name. Disable speculation without disabling the remote cache:

[cache]
prefetch_enabled = false

This mode keeps local hits, exact remote hits, and asynchronous uploads. It skips startup manifest warming, build-start advisory/fallback planning, direct prefetch requests, and remote key-cache population. The daemon watches the config file and restarts when it changes. If you use KACHE_PREFETCH_ENABLED instead, restart the daemon explicitly because an existing daemon cannot see a changed environment.

For cold or ephemeral CI, leave prefetch enabled and use build-shape-specific manifest keys and namespaces. If periodic discovery is useful but a 60-second full listing is too frequent, keep prefetch enabled and raise remote_key_cache_refresh_secs. Raising it reduces LIST frequency, but stale negative entries are authoritative for no more than five minutes; after that, exact misses fall through to a remote HEAD probe. Set the interval to 0 for one startup population with no refresh: positive entries and fallback planning still use that snapshot, while negative entries never suppress exact remote lookup.

Excluding Sources

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

[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.

Pinning config against env

By default every KACHE_* environment variable wins over the config file — convenient for CI overrides, but it means a stray machine-global export can silently change behavior. The riskiest case is KACHE_KEY_SALT: a leaked export would shift every cache key without a word.

Set ignore_env to make a pinned config authoritative:

[cache]
ignore_env = true
key_salt = "v3-toolchain-abc"

With this, kache ignores the KACHE_* overrides for file-backed settings (cache dir, max size, key salt, fallback, remote/planner settings, the toggles, etc.) and takes the file value (or built-in default) instead. When an ignored override is actually set, kache logs a warning naming it, so the suppression is never silent.

ignore_env is file-only by design — an env var can't re-enable env overrides, or the lockdown would be trivially undone by the same stray export it defends against. It deliberately does not cover bootstrap/operational vars that have no file representation (KACHE_CONFIG, KACHE_DISABLED, KACHE_LOG / KACHE_LOG_FILE / KACHE_PROGRESS, KACHE_NAMESPACE, KACHE_BASE_DIR) or S3 credentials (KACHE_S3_ACCESS_KEY / KACHE_S3_SECRET_KEY), which are secrets rather than config.

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. Native Linux OS-loaded outputs also key the host GNU libc or musl version, while portable rlibs and cross-target outputs exclude that host-only signal. The observable set is not complete for toolchain changes that leave every reported version unchanged — for example a custom or cross-target libc/sysroot replacement, a distro patch that retains the same upstream libc version, a hidden mold/linker change, 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:

[cache]
key_salt = "nixpkgs-a1b2c3d-mold-2.40"

Or compute it from the toolchain itself:

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:

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.

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.

extra_inputs is only evaluated when cargo invokes the compiler. Editing a tracked file in a warm in-place target — changing .sqlx/ without touching any .rs — doesn't re-invoke rustc on its own, so the key isn't recomputed and the prior artifact is restored. To make such edits re-key, your build script must emit a matching cargo:rerun-if-changed for the path (cargo then re-runs the crate when it changes). kache doctor flags crates that declare extra_inputs but whose build script won't re-trigger the compiler.

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.

Extra path prefixes

Use the file-only [paths].base_dirs list when automatic workspace/home/temp detection and the legacy single KACHE_BASE_DIR do not cover paths baked into artifacts—for example container mounts, /snap, /var/lib/flatpak, an AppImage mount, or a custom toolchain root:

[paths]
base_dirs = ["/snap", "/var/lib/flatpak", "/work"]

Entries must be absolute and contain no ... They need not exist on every machine, so a shared project config remains usable outside the container or sandbox. kache sorts the normalized entries independently of TOML order, assigns each one a distinct <BASE_DIR_N> key sentinel and /kache/base-dir-N compiler path, and uses the longest configured prefix when entries overlap. The configured roots feed both rustc and gcc/clang key inputs and emitted prefix maps. KACHE_BASE_DIR remains supported as the separate legacy <BASE_DIR> rule.

Path normalization removes distinctions and therefore merges cache keys. An over-broad root, or differently paired lists on two machines, can make genuinely different inputs look identical and restore a wrong artifact. C/C++ compiler prefix maps use raw byte-prefix semantics: for example, /work also matches /workspace. Rust mappings are path-component aware. Choose narrow, unambiguous roots, commit the same .kache.toml for every cache participant, and review this warning and kache's per-entry audit log when this list is active.

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:

[cache]
path_only_env_vars = ["BUILDCONFIG_RS", "MOZ_TOPOBJDIR"]

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

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.

Env vars that steer expansion

kache keys the compile-time environment a build bakes in — the env!() and option_env!() values rustc reports in dep-info. A proc macro that calls std::env::var while expanding is a different story: rustc has no way to report it, so the command line, the source hashes, and the --extern set are byte-identical whether or not the var is set, while the emitted artifact is not.

That is a real failure mode, not a hypothetical one. In #635 a two-phase build ran the same crate graph twice — once with BOLTFFI_BINDING_EXPANSION=1, where the #[export] macro strips itself from dependency crates and emits no trait impls, and once without. The dependency crate's rustc invocation was identical in both phases, so both compiles keyed the same and the normal build restored the stripped 264 KiB artifact instead of building the 620 KiB one. It surfaced as a missing trait impl at compile time; the same mechanism can just as easily produce a wrong binary that builds cleanly.

cache.key_env_vars declares the vars that steer expansion so the two modes get distinct entries:

[cache]
key_env_vars = ["BOLTFFI_*"]

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

export KACHE_KEY_ENV_VARS="BOLTFFI_BINDING_EXPANSION,BOLTFFI_SURFACE"

Semantics:

  • Exact names, or a trailing * prefix glob. BOLTFFI_* selects every var starting with BOLTFFI_; APP_MODE selects only that name. Matching is ASCII case-insensitive, so a Windows environment behaves like a Unix one. A * anywhere but the end is a literal character — A*B matches a variable actually named A*B — and kache warns when it sees one, since that is almost never what someone meant.
  • Turning it on re-keys the crate. The declared patterns are folded, not just the matched values — otherwise the build that leaves the vars unset would land back on the poisoned entry that made you reach for this setting. Expect one cold rebuild after adding or editing the list.
  • Only vars actually set are folded, as NAME=VALUE pairs. Set-to-empty and unset are distinct, matching what std::env::var reports.
  • Declaration spelling doesn't matter. The list is upper-cased, sorted and deduplicated before it is folded, so two teammates writing BOLTFFI_* and boltffi_* in either order still share a cache.
  • Values are folded exactly, as their raw OS bytes. See the warning below.
  • Off by default. An empty list has no effect; keys are byte-identical to not setting it.
  • Union-only. A misdeclared pattern can cost a cache miss, never restore a wrong artifact.

Declared values are not path-normalized, unlike most paths kache keys. A macro is free to paste a variable's value straight into the code it emits, so two checkout paths that would collapse to the same <BASE_DIR> sentinel can still produce different artifacts — normalizing them would reintroduce the wrong-hit this setting exists to prevent. The consequence is that a declared variable holding a machine-local path (BOLTFFI_ROOT=/home/alice/proj) makes that crate's key machine-specific and stops it sharing across hosts. Prefer declaring the switch a macro actually branches on (BOLTFFI_BINDING_EXPANSION) over a broad BOLTFFI_* glob that sweeps in path variables too.

Keep the list to vars that genuinely change what a macro emits. Naming something that varies per build — a timestamp, a job id, PWD — gives every compile its own key and disables caching for the whole workspace. "*" is a valid pattern and folds the entire environment, but for the same reason it is close to a cache-off switch, and it makes every credential in the process contribute to your cache keys.

Values are never written to logs or events — only variable names appear at KACHE_LOG=trace — but they do influence the resulting cache key, which is stored and (with a remote) transmitted. Treat a declared secret as hashed, not as hidden.

proc_macro::tracked_env would let a macro register these reads with the compiler and make this configuration unnecessary. It is still unstable, so for now the vars have to be declared. Other compiler caches share the limitation.

If you would rather not cache the affected crate at all, cache.exclude takes a source-path glob and bypasses kache entirely for those compiles.

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.

For setup and the current C/C++ support matrix, see C/C++ caching.

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:

[cc]
extra_allowlist_flags = ["-ffunction-sections", "-fdata-sections", "-fno-rtti"]

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

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.

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.

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=.

S3 credential resolution order

Remote S3 credentials are resolved in this order:

  1. KACHE_S3_ACCESS_KEY + KACHE_S3_SECRET_KEY
  2. Standard AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
  3. Static credentials in the selected profile (KACHE_S3_PROFILE, cache.remote.profile, AWS_PROFILE, then default)
  4. Legacy inline SSO, then credential_process for the selected profile
  5. Environment-based web identity, ECS/task credentials, then EC2 instance credentials

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

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

Composing with other compiler wrappers

kache works as a RUSTC_WRAPPER and composes with a second wrapper in either direction.

Behind another workspace wrapper (RUSTC_WORKSPACE_WRAPPER, e.g. clippy-driver): when cargo has both RUSTC_WRAPPER=kache and a RUSTC_WORKSPACE_WRAPPER set, it invokes kache <workspace-wrapper> <rustc> <args>. kache detects that the inner argument is itself a compiler, forwards the real rustc path as the workspace wrapper expects, and keys/caches around it. cargo clippy (which drives clippy-driver) is handled the same way — no extra configuration needed.

In front of another wrapper — when you want a second wrapper to cache the compiles kache declines: set KACHE_FALLBACK (or cache.fallback). kache then runs <fallback> <compiler> <args> for passed-through invocations, so e.g. sccache gets a chance at the compiles kache won't cache.

KACHE_DISABLED=1 makes kache a transparent shim (no lookup, store, or upload) while still stripping incremental flags — useful to temporarily bypass kache in a wrapper chain without unsetting the env var. Only 1 or true (case-insensitive) disable; 0/false/unset leave caching on.

Restore verification

By default, a hit checks recorded metadata and blob size, then restores the blob. Set KACHE_VERIFY_RESTORES=sampled to content-hash roughly one in sixteen hits, or KACHE_VERIFY_RESTORES=always to hash every restored blob before serving it:

KACHE_VERIFY_RESTORES=sampled cargo build

sampled is useful as cheap background coverage for silent disk corruption. always is stricter but adds an extra full read of every restored blob.

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:

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.

Containers and cross-compilation

kache's index (index.db) is a SQLite database, and SQLite needs reliable file locking from the filesystem it lives on — plus shared memory, in WAL mode. A normal local cache directory provides both. A cache directory shared across machine or OS boundaries does not.

The usual way to hit this is bind-mounting your host cache directory into a build container — for example cross, Docker, or Podman:

  • The directory is mounted from the host into the container, so index.db is opened from two different operating systems at once.
  • A kache daemon on the host typically holds index.db open in WAL mode for the duration of the build, and that WAL state cannot be shared with the process inside the container.

When kache can't open the index it falls back to building uncached — your build still succeeds, just with no cache hits or stores — and prints a one-time warning:

[kache] the cache index could not be opened after retries (...).
[kache] Caching is disabled for this build — compilation still succeeds,
[kache] just without cache hits or stores (everything builds uncached).
[kache] ...
[kache] → set KACHE_CACHE_DIR to a fast, local, single-machine path

Give the container its own cache directory. Point KACHE_CACHE_DIR at a path the container alone uses — ideally a dedicated volume so the cache persists across runs:

# inside the container / cross build — a container-local volume, NOT the host's cache dir
export KACHE_CACHE_DIR=/kache-cache

Don't bind-mount the host's cache directory into the container. Sharing it gains little anyway: host and cross builds target different platforms and feature sets, so they produce different cache keys and would not reuse each other's entries.

The same rule applies to network filesystems (NFS, SMB/CIFS, 9p) and any setup where more than one machine touches the same directory: keep KACHE_CACHE_DIR on a fast, local disk owned by a single machine. To reuse build artifacts across machines, configure an S3 remote or a separate filesystem remote. Only the filesystem remote's path belongs on the shared mount.

Available for:
Apple macOS logomacOSMicrosoft Windows logoWindowsLinux logoLinux
Download Kunobi