Quickstart.
Two halves: a server you run once, and an agent you install on each machine where you use Claude Code. About five minutes end to end.
1 · Run the server
The server is a SvelteKit app over SQLite. The image ships no data — the database is created empty in a mounted volume on first run.
$ git clone https://git.example.com/overspill.git && cd overspill/apps/selfhost $ cp .env.example .env # set API_TOKEN to something long and random $ openssl rand -hex 32 $ docker compose up -d --build
The dashboard comes up on the port set in docker-compose.yml. There is no first-run
wizard and nothing to configure in the UI — the whole configuration is the environment.
SHOW_TRANSCRIPTS=true.
2 · Install the agent
On each machine where you use Claude Code. Requires Node 20 or newer; there is nothing to
clone and no jq.
$ npm install -g overspill # or run it without installing: npx overspill <command> $ overspill login --url https://spend.acme.dev --token $TOKEN $ overspill backfill
login verifies before it saves. Reachability and authorisation are probed
separately, so "wrong address" and "wrong token" are different messages and nothing is written
on failure. The config lands in ~/.overspill/env at mode 0600.
backfill is a one-off import of everything already on disk, including
subagents/agent-*.jsonl — which is where a lot of Sonnet and Haiku usage
actually lives.
3 · Keep it current
Register a Claude Code Stop hook and every turn triggers a detached, incremental
sweep.
$ overspill hook install $ overspill status # confirm it took
The installer edits ~/.claude/settings.json directly. It is additive and
idempotent: other hooks and unrelated blocks survive both install and remove, the file is
backed up before every write, and it refuses to write at all if the file is
unparseable rather than clobbering it. overspill hook remove reverses it.
Prefer a daemon? overspill watch --interval=<ms> sweeps continuously in the
foreground instead.
Capture levels
What a machine is allowed to send. The server applies its own ceiling as well, so the effective level is the lower of the two — storing conversation text means opting in at both ends.
| Level | Sends |
|---|---|
| metadata | Counts, models, timings, costs, tool names. The default. |
| commands | The above, plus redacted tool inputs. |
| full | The above, plus prompt and response text. Self-host only. |
$ overspill capture # show the current level $ overspill capture commands # raise it on this machine
metadata, raise BODY_SIZE_LIMIT on the server
— BODY_SIZE_LIMIT=64M. Batches carrying prompt text run to several megabytes and
the adapter's default is 512KB. Without it the ingest endpoint returns a
413 naming the variable and quoting the sizes involved.
Environment variables
Server
| Variable | Purpose |
|---|---|
API_TOKEN |
Bearer token the agent presents to /api/ingest.
Required. Generate with openssl rand -hex 32.
|
DB_PATH |
SQLite file. In Docker this is /data/overspill.db on the mounted volume. |
PORT |
Listen port inside the container. |
MAX_CAPTURE_LEVEL |
The most this server will store, whatever an agent sends. Unset or
unrecognised means metadata — a typo must never widen capture.
|
SHOW_TRANSCRIPTS |
Gates the transcript view and full-text search. Unset means hidden. This gates
display; MAX_CAPTURE_LEVEL gates ingest.
|
BODY_SIZE_LIMIT |
Raise to 64M if any agent runs above metadata. |
Agent
Normally written by overspill login into ~/.overspill/env; the
environment overrides the file if you would rather set them yourself.
| Variable | Purpose |
|---|---|
OVERSPILL_URL |
Base URL of the server. |
OVERSPILL_TOKEN |
Must match the server's API_TOKEN. |
OVERSPILL_CAPTURE |
The most this machine will send. Defaults to metadata. |
CLI reference
overspill login --url <url> --token <token> [--capture <level>] Save and verify this machine's connection settings overspill backfill Import every transcript already on disk (one-off) overspill sweep [--quiet] [--detach] Import anything new since the last run overspill watch [--interval=<ms>] Sweep continuously in the foreground overspill hook install Capture each turn automatically (Claude Code Stop hook) overspill hook remove Remove that hook overspill status Show configuration, hook state and server reachability overspill capture [level] Show or set what may leave this machine overspill --help | --version
Troubleshooting
The dashboard is empty
Run overspill status. If the server line says reachable and the hook is
installed, run overspill backfill once — the hook only captures turns that
happen after it was installed.
413 from the ingest endpoint
A capture level above metadata without BODY_SIZE_LIMIT raised on the
server. The error message names the variable and the actual sizes.
The cost is $0 for a model that clearly cost something
That model has no rate card. The dashboard says so in an amber banner above the headline figure, listing the model and the tokens involved. Add a rate to the pricing table, or add the model to the free-model patterns if it is locally hosted and genuinely free. Locally hosted models are excluded from the warning deliberately — warning about them would train you to ignore the banner.
Lowering the capture level did not remove old content
Correct, and it will not. The merge behaviour responsible is load-bearing for assembling tool calls that arrive in two separate batches. Purging previously stored content needs a separate explicit operation.
Latency is missing on some turns
Latency is computed within a sweep. When a turn's parent line was pushed in an earlier sweep, the value is left null rather than guessed.
overspill status — it reports configuration, hook state and
server reachability without disclosing your token.