kache 1.2.0

Compiler cache for Rust, C/C++ and CUDA, with S3, GCS, shared-filesystem and OCI remotes.
---
title: Daemon lifecycle
description: Start, stop, install, update, and troubleshoot the daemon
---

## Commands

```bash
kache daemon             # status
kache daemon start       # background process; waits for readiness
kache daemon stop        # requests drain and waits for ownership release
kache daemon restart     # service-manager restart or manual replacement
kache daemon run         # foreground process
kache daemon log         # follow logs
```

Use `KACHE_LOG=kache=debug kache daemon run` for foreground debugging. Stop the background daemon first with `kache daemon stop`: while another daemon serves this cache, `kache daemon run` exits at once.

When an installed service manages this cache, `start`, `restart` and automatic starts start the service instead of spawning a process. On macOS, Kache loads the job with `launchctl bootstrap` if launchd no longer has it.

The service restarts the daemon only when it fails. After `kache daemon stop`, an idle timeout or a config change, the daemon exits cleanly and stays stopped until something starts it through the service, such as the next build that uses a remote, `kache stats`, `kache monitor` or `kache daemon start`.

Every daemon, and every other `kache` command, appends info-level lines to a diagnostic log: `~/Library/Logs/kache/kache.log` on macOS, and `kache.log` in the default cache directory elsewhere (`~/.cache/kache/kache.log` on Linux, `%LOCALAPPDATA%\kache\kache.log` on Windows). The log stays there when `cache.local_store` or `KACHE_CACHE_DIR` moves the store. `KACHE_LOG_FILE` changes its filter. A daemon that Kache starts without the service also writes its stderr to `<runtime_dir>/daemon.log`: warnings, errors and the reason a start failed.

`kache daemon log` follows the diagnostic log on macOS and Windows. Until that file exists, it follows the service's `~/Library/Logs/kache/err.log` on macOS and `<runtime_dir>/daemon.log` on Windows. On Linux it follows the `kache.service` journal only, which is empty for a daemon started without the service.

On Unix, a daemon that a build starts runs in its own session, so Ctrl-C in the build's terminal does not stop it. On Windows, a daemon started under Cargo stays in Cargo's job object and stops when the build is interrupted. Install the service to avoid that.

## Install a login service

```bash
kache daemon install
```

`kache init` installs this service unless you pass `--no-service` or, on Linux, no systemd user session is available. Without the service, it starts the daemon directly. The install also starts the daemon.

On macOS it writes `~/Library/LaunchAgents/ninja.kunobi.kache.plist` and sends the service's output to `~/Library/Logs/kache/`. On Linux it writes and enables the `kache.service` user unit in `~/.config/systemd/user/` and tries to enable lingering, so the daemon keeps running after you log out. This needs a systemd user session. Containers and most CI runners have none; start the daemon there with `kache daemon start`. On Windows it registers the Task Scheduler task `kache-daemon`, which runs `kache daemon run` at logon. If `schtasks` is denied access, run the install from an elevated terminal.

The service records the same upgrade-safe path as the [C/C++ shims](/docs/getting-started/c-cpp#upgrades), such as Homebrew's `opt` link or a Nix profile, rather than a versioned path that an upgrade removes. The install prints the path it recorded. When no such path reaches the running binary, it records the binary's own path and says to rerun `kache daemon install` after each upgrade. `kache doctor` reports a service whose binary is missing, not executable, or a different file from the running Kache.

Remove the service with:

```bash
kache daemon uninstall
```

On recent macOS releases, the first connection to a cache server on the local network can show a Local Network prompt for `kache`. Allow it; you can change the decision later under System Settings > Privacy & Security > Local Network. If the installed daemon fails with `No route to host (os error 65)` for a LAN endpoint, reinstall the service with the current binary. If you still cannot grant the permission, run `kache daemon uninstall`. The next build that uses the remote then starts the daemon from your terminal, under the terminal's permission.

## Updates and config changes

Kache compares semantic release versions when deciding whether to replace a daemon. A client from a newer release can request a drain and replacement. Rebuilding or copying the same release does not request an upgrade. Build metadata such as `+local` does not affect release precedence. An older client keeps a newer daemon running.

Legacy clients and daemons can omit release metadata. A missing or invalid version never triggers a replacement by release. Kache 1.0.x and earlier compare binary modification times instead, as described below. Protocol and cache-format checks still apply.

`kache doctor` reads daemon stats without requesting a drain. Use `kache doctor --fix` to replace a daemon from an older release. Use `kache daemon restart` to load a local rebuild with the same version, or to replace a daemon whose release cannot be determined.

The daemon checks its config file, and the host config when there is one, every 15 seconds. When either changes, it drains and exits, and the next start loads the new settings. Run `kache daemon restart` to apply a change at once. `kache daemon start`, `kache daemon restart` and automatic starts drop the settings that select the remote from the environment (`KACHE_S3_BUCKET`, `KACHE_S3_ENDPOINT`, `KACHE_S3_REGION`, `KACHE_S3_PREFIX`, `KACHE_S3_PROFILE`, `KACHE_S3_USER_AGENT`, `KACHE_LOCAL_ONLY`), so set these in the config file. To try remote variables from a shell, run `kache daemon run` in the foreground.

`KACHE_REMOTE_READONLY` remains inherited so a daemon started by a read-only build cannot publish remote data. The CI variables that make pull request and other untrusted jobs read-only are inherited too. That restriction lasts for the daemon's lifetime. To enable uploads after clearing the flag, run `kache daemon restart` from the writable environment. Other environment is also fixed when the process starts. An installed service uses its stored environment; update that environment before restarting it.

A writable build can still reach a read-only daemon that shares its runtime directory, for example when a pull request job started the daemon on a runner that keeps it between jobs. kache-action gives each job its own runtime directory, so its jobs are not affected. The build succeeds, but the daemon skips its uploads and logs a warning, at most once every five minutes. `kache stats` shows `Uploads off` for a read-only daemon and warns when run from a writable environment, and `kache doctor` reports it as an issue. `kache save-manifest` prints a warning after it publishes a manifest, so the CI job's log shows it.

Rust uploads skipped this way stay queued, and GC keeps their entries while they wait. A writable daemon uploads them when it starts; a daemon that starts read-only drops them from the queue. C/C++ compiles handed to a read-only daemon are stored locally but never queued.

### Switching to or from 1.0.x

Kache 1.0.x and earlier compare the modification times of the `kache` binaries instead of releases. Later releases still send and report that time, so the binary that is newer on disk can retire the other release's daemon:

- After an upgrade from 1.0.x, the old daemon drains on the next build that uploads, prefetches or hands it a C/C++ compile.
- After a downgrade to 1.0.x, 1.0.x commands that check the daemon replace the newer one, for example `kache stats`, `kache monitor`, `kache daemon start` or `kache doctor --fix`. Builds and plain `kache doctor` do not, although `kache doctor` from 1.0.x says the daemon is shutting down and that the next build replaces it.

Run `kache daemon restart` to switch right away, or when the binary you switched to is not newer on disk. Nix store paths are an example: they all share one modification time.

Use one release at a time. When both stay in use, for example because one project pins 1.0.x, and the 1.0.x binary is newer on disk, each release's commands replace the other's daemon: 1.0.x by modification time, later releases by version. With a `kache monitor` from each release open, that happens on every poll. Remove the old binary or stop using it.

## Shared lifecycle

Kache uses `kunobi-daemon` for process locks, verified readiness, request drain,
and exclusive replacement. The old daemon releases its cache resources before
the replacement starts. A startup timeout cannot report “Background cache:
running” or authorize killing unfinished work.

An installed service owns only its configured runtime directory. A command using
another cache instance starts that instance independently. Lock files remain on
disk after shutdown; deleting them can split ownership between two processes.

New daemons advertise a separate Protobuf control endpoint. It authenticates the
OS peer and checks the Kache service identity and cache-instance identity. Older
daemons retain the bounded legacy health/shutdown exchange. Failed binary
negotiation never falls back to that legacy exchange.

Kache handles cache compatibility, the upload spool, service installation and
compiler fallback itself. Upload intents it has accepted survive shutdown in
the spool, and `kunobi-daemon` never owns or cancels them. Each program that
uses `kunobi-daemon` decides how to export its lifecycle observations.

Transient remote errors keep their upload intents for retries and restart
replay. A terminal failure, including rejected credentials or invalid remote
configuration, removes the intent and abandons automatic reupload. The local
cache entry remains until GC evicts it; fixing credentials later does not
replay an intent that has already been retired. This also abandons duplicate
requests folded into that in-flight upload. A request arriving after retirement
creates a new intent if the local entry still exists.

## Idle shutdown

Idle shutdown is off by default. Set a timeout in seconds to turn it on:

```toml
[cache]
daemon_idle_timeout_secs = 600
```

The equivalent environment variable is `KACHE_DAEMON_IDLE_TIMEOUT`. Every connection counts as activity, including the stats polling of an open `kache monitor`. After an idle exit, the next build that uses a remote starts the daemon again. A local-only build does not; `kache stats`, `kache monitor` or `kache daemon start` do.

## Offline behavior

When the daemon cannot be reached:

- local hits and local stores continue
- exact remote checks and prefetch are skipped
- validated upload intents remain in `<cache_dir>/upload-queue`
- a later daemon replays queued uploads before retiring their files

The monitor falls back to direct store and event-log reads. When the daemon stays offline, run `kache daemon status`: it shows where the daemon logs and where a failed start left its error.

If `kache daemon start` launches a daemon itself and that daemon exits before it is ready, the command prints what the daemon wrote to `<runtime_dir>/daemon.log`, up to 2 KiB, and exits with status 1. If no daemon is ready within 8 seconds, it prints `daemon did not start within timeout` and exits with status 1. Later, `kache daemon status` says where the reason is: the file `<runtime_dir>/daemon.log` for `daemon start` and automatic starts, the file `~/Library/Logs/kache/err.log` under launchd, or the command `journalctl --user -u kache.service` under systemd. The Windows task keeps no startup output, so run `kache daemon run` to see the error.