---
title: Lifecycle
description: Starting, stopping, installing as a service, and how the daemon handles binary updates.
---

# Lifecycle

## Starting and stopping

```sh
kache daemon start      # start in background, blocks until the socket is ready
kache daemon stop       # graceful shutdown (drains pending uploads, up to 30s)
kache daemon restart    # restart in place (service-manager kickstart, else stop + fresh spawn)
kache daemon            # status: binary version/epoch, service install state, running/stopped, socket path, log location, and (if running) the daemon's version/epoch
kache daemon log        # stream the daemon log (follows like tail -f)
```

`kache daemon restart` recovers a wedged daemon in three tiers: it first asks the service manager (launchd/systemd) to kickstart it, then falls back to killing any lingering `kache daemon run` processes and wiping stale socket/lock/state files, then spawns a fresh background daemon.

`kache daemon run` starts the daemon in the foreground. This is mostly useful for debugging. By default only warnings and errors print to stderr; set `KACHE_LOG=kache=info` (or `kache=debug`) for verbose daemon logs.

## Installing as a system service

For the daemon to survive reboots and start automatically, install it as a system service.

<Tabs items={["macOS (launchd)", "Linux (systemd)"]}>
  <Tab value="macOS (launchd)">
    ```sh
    kache daemon install
    ```

    This creates a launchd plist at `~/Library/LaunchAgents/ninja.kunobi.kache.plist` and loads it. The daemon will start on login and restart if it crashes.

    To remove it:

    ```sh
    kache daemon uninstall
    ```
  </Tab>
  <Tab value="Linux (systemd)">
    ```sh
    kache daemon install
    ```

    This creates a systemd user unit (`kache.service`) and enables it. The daemon runs under your user session.

    ```sh
    systemctl --user status kache.service
    journalctl --user -u kache.service   # logs (or use `kache daemon log`)
    ```

    To remove it:

    ```sh
    kache daemon uninstall
    ```
  </Tab>
</Tabs>

## Automatic restart on binary update

When you update kache, the old daemon and the new wrapper would disagree on RPC protocol versions. kache handles this automatically.

Each process tracks a **build epoch** — the modification time of the kache binary. The daemon reads the client's epoch from incoming Upload/Stats/BuildStarted requests; when a client binary is newer, the daemon sets its own shutdown flag and self-terminates once in-flight work drains. If it was installed as a service, launchd/systemd restarts it with the new binary; otherwise the next upload or CLI/monitor command auto-starts it. The next RPC call hits the fresh daemon. You never need to manually restart the daemon after an update.

<Callout type="info">
Restarting the daemon adds no latency to in-progress rustc invocations: the remote-check hot path never starts the daemon (it skips straight to compilation on a miss). Only the background upload path will lazily (re)start a downed daemon.
</Callout>

## Idle shutdown

The daemon can auto-exit after a stretch with no connections, so zombie daemons don't accumulate when you aren't building. It is re-spawned on the next build.

```toml
# config.toml
daemon_idle_timeout_secs = 600   # exit after 10 min idle; 0 disables
```

You can also set `KACHE_DAEMON_IDLE_TIMEOUT` (seconds). **The default is 600 seconds (10 minutes).** Set it to `0` to disable the watchdog so the daemon runs indefinitely until you stop it, the binary updates, or the OS signals it.

## Lifecycle internals

A few behaviors worth knowing about:

- **Single instance**: a second `kache daemon run` exits cleanly (code 0) if another process holds the run lock or the socket is already live, so launchd/systemd `KeepAlive` won't loop. Stale socket files are removed on startup when nothing is listening.
- **Background log file**: auto-started daemons write stderr to `<socket>.log` next to the daemon socket (Linux `~/.cache/kache/daemon.log`, macOS `~/Library/Caches/kache/daemon.log`). It is reset to a `--- log rotated ---` marker once it grows past 2 MiB. `kache daemon log` tails this file.
- **Graceful drain**: on shutdown the daemon closes its upload queue and waits up to 30 s for in-flight S3 uploads to finish before aborting workers.

## Offline behavior

If the daemon is down or unreachable when the wrapper makes an RPC call, the wrapper continues without it. This affects:

- **Remote checks**: skipped — the wrapper goes straight to compilation on a local miss
- **Upload jobs**: the wrapper tries to (re)start the daemon and re-send; the job is dropped only if the daemon can't be started. It never fails the build.
- **Prefetch**: skipped for the current build session

The monitor shows `daemon: offline` when the daemon hasn't responded in the last refresh cycle. Common causes:

| Symptom | Likely cause |
|---|---|
| `daemon: offline` after reboot | Service not installed, or install is user-scoped and session hasn't started |
| `daemon: offline` after update | Epoch mismatch restart in progress — wait a second and refresh |
| `daemon: offline` continuously | Check `kache daemon log` for startup errors |

When the monitor shows `daemon: offline`, it falls back to reading the local store and event log directly, so the Build and Store tabs still show accurate data.
