overspill
Self-host it
Pre-release · Node 20+ · Docker for the server

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.

The self-hosted dashboard has no authentication. Put it behind a reverse proxy, a VPN or a tailnet. Do not expose it to the public internet. The transcript view and full-text search are additionally hidden unless 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
Above metadata, raise BODY_SIZE_LIMIT on the serverBODY_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.

Something not covered here? hello@overspill.dev. Include the output of overspill status — it reports configuration, hook state and server reachability without disclosing your token.