Environment variables
Every knob is optional — chat-recall runs with sane defaults. This is the single
reference for the CHAT_RECALL_* variables, plus the server's auth and billing
ones (grep the source for the authoritative behavior; defaults below are current
at time of writing).
Collector / sync (the chat-recall CLI + chat-recall-watch daemon)
| Variable | Default | What it does |
|---|---|---|
CHAT_RECALL_UPLOAD_TIMEOUT_MS | 90000 | Per-request abort timeout for conversation uploads. |
CHAT_RECALL_FULL_BUILD_MAX_MB | 64 | Transcripts larger than this aren't fully materialized on a FULL sync — only the newest tail ships (prevents OOM on huge sessions). |
CHAT_RECALL_TAIL_APPEND | on | Set 0 to disable incremental tail-append and force FULL syncs (emergency off-switch for the offset-continuity path). |
CHAT_RECALL_EXTERNAL_SCANNERS | off | Set 1 to enable the external secret detectors (gitleaks/trufflehog). Off by default: they are third-party subprocesses that need pre-redaction text materialized on disk, and coverage would vary per device. Built-in regex redaction + findings and tenant rules run for everyone regardless. |
CHAT_RECALL_SCAN_SECRETS | on | Set 0 to skip the external secret scanners even when they're enabled above (e.g. to keep a huge backfill fast). |
CHAT_RECALL_BATCHSCAN_MAX_MB | 64 | Cap on pre-redaction text the external-detector batch scan keeps on disk at once. The batch dir is scanned and deleted each time it crosses this, bounding what a SIGKILL/OOM can strand in /tmp. |
CHAT_RECALL_INCLUDE_FUZZY | off | Set 1 to keep low-precision detector findings (generic-api-key, URI, …) instead of dropping them. |
CHAT_RECALL_SYNC_TRACE | off | Set 1 to print per-phase progress + heap/RSS to stderr — how walk OOMs get localized. |
CHAT_RECALL_REDACT_INDEX | — | Index-time redaction toggle (sync-path redaction is always on regardless). |
CHAT_RECALL_AUTO_UPDATE | on (self-host) | Controls the daemon's checksum-pinned same-origin self-update. |
CHAT_RECALL_WATCH_WINDOW_DAYS | 14 | How recently a transcript must have been written for the daemon to put a file watcher on it. chokidar takes one watch descriptor per matched file, and a finished transcript never changes, so watching every session ever written cost 14,000 descriptors and 900 MB of heap on a machine with 13,929 of them. Sessions older than the window still sync — the 15-minute heartbeat does a full ledger walk — they just are not detected the instant they change. |
CHAT_RECALL_OIDC_ISSUER | — | Fallback OIDC issuer for chat-recall login SSO when the server doesn't advertise one and --issuer isn't passed. |
Auto code-intelligence (collector)
| Variable | Default | What it does |
|---|---|---|
CHAT_RECALL_CODE_INDEX | on | Set 0 to disable automatic codeindex discovery/indexing. |
CHAT_RECALL_CODE_INDEX_MINUTES | 180 | How often the code-index pass runs. |
CHAT_RECALL_CODE_INDEX_MAX | 50 | Max workspaces indexed per pass. |
CHAT_RECALL_CODE_INDEX_DAYS | 120 | Recent-activity window for discovering workspaces. |
CHAT_RECALL_WITH_CODEINDEX | — | Force-download the codeindex companion during init. |
Path overrides
Point the collector at non-standard tool locations (useful for fixtures/tests):
| Variable | Overrides |
|---|---|
CHAT_RECALL_CLAUDE_HOME | ~/.claude |
CHAT_RECALL_CODEX_HOME | ~/.codex |
CHAT_RECALL_AGY_HOME | the agy tool home |
CHAT_RECALL_CURSOR_HOME | ~/.cursor (the cursor-agent CLI store) |
CHAT_RECALL_CURSOR_IDE_HOME | ~/.config/Cursor (the DESKTOP app — note the capital C; ~/.config/cursor is the CLI's auth dir) |
CHAT_RECALL_HERMES_HOME | ~/.hermes, or %LOCALAPPDATA%\hermes on Windows; HERMES_HOME when it is set. Named profiles in its profiles/ folder are read too. |
CHAT_RECALL_PI_HOME | ~/.pi/agent (Pi's agent dir); PI_CODING_AGENT_DIR when it is set. |
CHAT_RECALL_PI_SESSION_DIR | <agent dir>/sessions, unless PI_CODING_AGENT_SESSION_DIR or an absolute sessionDir in <agent dir>/settings.json names another folder. |
CHAT_RECALL_OPENCODE_DB | ~/.local/share/opencode/opencode.db (full file path) |
CHAT_RECALL_DATA_DIR | ~/.chat-recall (ledger, shadow archive, credentials) |
Server / storage (self-host)
Set on the server container, not the CLI. See the quick-start header of
docker-compose.yml for the canonical set.
| Variable | What it does |
|---|---|
CHAT_RECALL_STORAGE | postgres (product) — required; the server fails closed rather than guessing a backend. (sqlite exists for unit tests only, not a user-facing mode.) |
CHAT_RECALL_DATABASE_URL / ..._URL_RO | Postgres connection string(s) for bring-your-own-Postgres. |
OIDC_ISSUER | The server's OIDC issuer — advertised to CLIs via /api/capabilities for SSO login. |
ADMIN_KEY, AUTH_PROVIDER | Admin key and auth mode (none for a single-tenant local box). |
SECRET_RESCAN | 0 disables the daily re-scan of ALREADY-STORED (redacted) text with today's rules. That pass is the only thing that can notice a secret a client's redactor missed — it is in the DB in cleartext at that point — so leave it on unless you have a reason. |
SECRET_RESCAN_LIMIT | Sessions per re-scan run (default 2000, freshest first). |
SECRET_RESCAN_EXCLUDE_TENANTS | Comma-separated tenants to skip (default synccheck). |
RAW_ARCHIVE_S3_BUCKET | Keeps the gzipped session archive in an S3-compatible bucket instead of the raw_sessions.gz column. Unset (the default) stores it in Postgres, which is what a self-host with no bucket does. On the hosted database that column was 1232 MB of 3564 MB, all of it TOAST, and only one read path touches it. |
RAW_ARCHIVE_S3_ENDPOINT | S3 endpoint, e.g. https://s3.example.com. Required with the bucket. |
RAW_ARCHIVE_S3_ACCESS_KEY_ID / ..._SECRET_ACCESS_KEY | Credentials. Give them an identity scoped to this bucket: object storage has no row-level security, so the only thing keeping one tenant's archive from another is that the key is reachable only through a raw_sessions row, which is behind the tenant and author policies. |
RAW_ARCHIVE_S3_REGION | Region for the signature (default us-east-1). |
Existing archives keep working when the bucket is turned on: a row with bytes in
gz and no key is read from the column, and one with a key is read from the
bucket, so both shapes coexist. Move them with
npx tsx scripts/backfill-raw-to-object-store.ts, which is safe to stop and
re-run and verifies every object before clearing a row.
Other server-internal flags (CHAT_RECALL_EDITION, _ROLE, _SERVER_MODE,
_TENANT, _FEATURE_*, _VECTOR_PARTITIONS, _TELEMETRY) tune SaaS/edition
behavior; consult the server source before setting them.
Billing (Stripe)
Set on the server. Billing is entirely optional: with STRIPE_SECRET_KEY
absent the server boots with billing off, and a self-host box never needs any of
these. Stripe owns the amounts — the server reads prices back from Stripe, so a
price change in the dashboard needs no deploy.
Required to sell anything:
| Variable | What it does |
|---|---|
STRIPE_SECRET_KEY | Secret API key (sk_live_… / sk_test_…). Its presence is what turns billing on. |
STRIPE_WEBHOOK_SECRET | Endpoint signing secret (whsec_…). The webhook verifies over the raw bytes; without this it rejects every event. |
BILLING_PLANS | JSON array of the plans this deployment sells: [{"key":"solo-monthly","label":"Solo","priceId":"price_…","seats":"fixed"}]. seats is fixed (quantity 1) or per_seat (with optional minSeats/maxSeats). A plan with contact and no priceId is contact-only: checkout refuses it and returns the address. Malformed JSON degrades to the legacy single plan below — it never crashes the server. |
STRIPE_PRICE_ID | The pre-catalogue single plan. Used only when BILLING_PLANS is unset or unparsable. Keep it for an existing one-plan deployment; prefer BILLING_PLANS for anything new. |
Redirects. Set PUBLIC_URL and the three below are derived from it; set one
explicitly to send a return somewhere else:
| Variable | What it does |
|---|---|
STRIPE_SUCCESS_URL | Where checkout returns after a successful subscribe. |
STRIPE_CANCEL_URL | Where checkout returns when the buyer backs out. |
STRIPE_PORTAL_RETURN_URL | Where the customer portal returns. |
Optional:
| Variable | Default | What it does |
|---|---|---|
STRIPE_PRODUCT_TAG | chat-recall | Which product this deployment sells. Set it if one Stripe account sells more than one product. Stripe fans an event out to every endpoint subscribed to that event type — it does not route by product — so another product's customer.subscription.updated arrives here too. The tag is stamped on every checkout and checked on every event, so buying product B cannot grant a paid entitlement here. |
STRIPE_AUTOMATIC_TAX | off | Set 1 to let Stripe Tax calculate VAT/sales tax at checkout and collect the buyer's VAT id. Warning: enable this only after Stripe Tax settings report status: active and every live price carries a tax behavior. automatic_tax needs a tax behavior on each line, so turning it on too early fails every checkout on the account — a revenue outage, not a degraded experience. VAT id collection is part of the same switch on purpose: tax calculation without it charges consumer VAT on B2B sales that should have been reverse-charged. |
STRIPE_TRIAL_DAYS | 7 | Length of the no-card trial. |