Configuration
roost is almost entirely stateless. It reads a handful of environment variables, and it writes to exactly two small JSON files plus one optional credentials file — nothing else ever touches disk.
Environment variables
Section titled “Environment variables”| Variable | Default | Gates |
|---|---|---|
ROOST_BACKEND | google | Which backend search, rates, and dates use; overridden per-call by their --backend flag. brands, doctor, and schema have no --backend flag of their own, but still resolve through this same variable (default google) — so setting it to serpapi routes those commands through the SerpApi backend too, and doctor/brands will fail with AUTH_REQUIRED if no key is configured. |
ROOST_SERPAPI_KEY | unset | The SerpApi key, checked first in the auth resolution order (below). Only read when the serpapi backend is selected (via --backend or ROOST_BACKEND). |
ROOST_STATE_DIR | unset | Overrides the directory for both ratelimit.json and places.json. When unset, falls back to $XDG_STATE_HOME/roost (or ~/.local/state/roost). |
XDG_STATE_HOME | unset | Base for the state directory when ROOST_STATE_DIR isn’t set. |
XDG_CONFIG_HOME | unset | Base for the credentials file directory: $XDG_CONFIG_HOME/roost/credentials (or ~/.config/roost/credentials). |
ROOST_RELEASES_URL | unset | Overrides the source roost version --check polls for the latest release. Must be https:// (any host) or http:// to localhost/127.0.0.1/::1 — anything else is silently ignored and the default GitHub releases lookup is used instead. |
ROOST_BACKEND and --backend both select the backend; the flag wins when both are given.
Neither is required — the default is the keyless google backend, so a bare roost search
works with no environment configured at all. See backends for what
each backend can and can’t do.
State directory
Section titled “State directory”roost runs as a fresh process on every invocation — an agent spawns one process per call — so anything that needs to persist across calls (rate-limit timing, the place-id cache) has to live on disk. That directory is:
$ROOST_STATE_DIR # if set$XDG_STATE_HOME/roost # else, if set~/.local/state/roost # elseTwo files live there, and roost creates the directory (mkdir -p-style) the first time it
needs to write to it.
ratelimit.json
Section titled “ratelimit.json”Backs the politeness layer described in politeness: minimum
request spacing, a small burst allowance, and a circuit breaker that opens after an
upstream block. It is keyed by backend name, so google and serpapi track independently
(in practice only google is throttled — serpapi and --no-throttle skip the throttle
guard entirely):
{ "google": { "last_request": 1755781234.12, "window": [1755781228.9, 1755781234.12], "consecutive_blocks": 0, "blocked_until": 0 }}last_request— epoch seconds of the most recent request; enforces the minimum interval.window— timestamps from the last 600 seconds, used to allow a burst of 2 (one logical search costs two requests: resolve the place, then fetch results).consecutive_blocks— how many times the circuit breaker has tripped in a row; indexes into the backoff schedule[30, 60, 120, 300, 600, 1800]seconds.blocked_until— epoch seconds the breaker stays open until, set after an upstream 429/CAPTCHA. A clean response resets bothconsecutive_blocksandblocked_untilto 0.
roost writes this file with mode 0600 after every throttled request.
places.json
Section titled “places.json”Caches the Google place id (a Freebase-style /m/… id, called mid) resolved for each
location string, so repeat searches for the same city cost one upstream request instead of
two:
{ "austin+tx": { "mid": "/m/0vzm", "name": "Austin, TX", "at": 1755781200.0 }}Keyed by a slugified location (lowercased, whitespace collapsed to +). Entries are
treated as fresh for 30 days (at plus 30 days); after that roost re-resolves the place on
next use. A cache write that fails (read-only filesystem, permissions) is treated as a
slowdown, not an error — search still works, it just re-resolves every time.
Credentials file
Section titled “Credentials file”Only used for the optional serpapi backend’s API key — the default google backend never
touches this file, and never needs an account or key of any kind.
$XDG_CONFIG_HOME/roost/credentials # or ~/.config/roost/credentialsFormat is key=value lines, one credential per line:
serpapi-key=your-serpapi-key-hereroost creates this file with mode 0600 (owner read/write only) whenever it has to fall
back to it. If the file already exists with looser permissions, roost auth login surfaces
a warning telling you to chmod 600 it (checked at the moment it writes to the file) —
roost does not silently tighten a file that already exists. roost auth status does not
re-check the file’s permissions; it only reports whether a key is present.
Setting the key
Section titled “Setting the key”$ echo "$SERPAPI_KEY" | roost auth login --token-stdinThe key is always read from stdin (or the ROOST_SERPAPI_KEY environment variable) —
never from a command-line argument, where it would leak into ps, /proc, shell history,
and an agent’s own command log.
Resolution order
Section titled “Resolution order”When roost needs the SerpApi key, it checks, in order, stopping at the first hit:
ROOST_SERPAPI_KEYenvironment variable- OS keyring (service
roost, usernameserpapi-key) - The
0600file above
roost auth login prefers the OS keyring and only falls back to the file when no keyring
backend is available (common on headless boxes/containers) — that fallback is automatic,
you don’t choose it. roost auth logout removes whichever copy is stored locally; it does
not revoke the key with SerpApi itself.
Resetting state
Section titled “Resetting state”Deleting the state files is always safe — roost recreates them as needed, and nothing else depends on their contents surviving.
$ rm "$XDG_STATE_HOME/roost/ratelimit.json" # or ~/.local/state/roost/ratelimit.json$ rm "$XDG_STATE_HOME/roost/places.json"Reasons to do it:
ratelimit.json— you’re stuck behind a circuit-breaker cooldown (exit code20,BLOCKED) and want to force a retry immediately instead of waiting out the backoff. Deleting it clearsblocked_untiland the request-spacing history; it does not change whether the underlying block is still in effect upstream, so a deleted file can just earn a fresh block.places.json— a location string is resolving to the wrong city, or you suspect a stale/incorrect cached place id. Deleting it forces re-resolution on the next search.
To reset only the credentials file:
$ roost auth logoutor delete $XDG_CONFIG_HOME/roost/credentials directly.
Nothing else is written
Section titled “Nothing else is written”Those three files are the entire disk footprint. roost writes no logs, no cache of search results, no config file of its own settings — every other option is either a CLI flag or one of the environment variables above, resolved fresh on each run.