Nothing in local:up hot-reloads
The web tier is a prebuilt production bundle with no Vite process, and every server process on that path is spawned without --watch — RECEIPT_SERVER_WATCH is never set here — so each holds its module graph for its process lifetime. Plan on an explicit action per change.
Model credentials come from the database, not the environment
Model credentials are organization-scoped rows in Postgres, not a process variable. Funding resolves in this order: a Slack-originated billing request id beginningslack_evt_ may authorize platform credit first; otherwise an encrypted key for the organization or workspace, decrypted with BYOK_ENCRYPTION_KEY_B64 — this is the normal path; and only with a billing request id, the process-wide OPENAI_API_KEY as platform credit.
So setting OPENAI_API_KEY in your environment file alone will not make agent runs work. Sign up locally, create an organization, and paste a provider key into its model settings. receipt doctor reflects this deliberately: it prints the fixed line provider auth: organization BYOK required and performs no model-key check at all.
With neither a stored key nor platform credit you will see, in web chat:
Platform-funded OpenAI access is unavailable: OPENAI_API_KEY is not configured.Platform-funded OpenAI embeddings are unavailable: OPENAI_API_KEY is not configured.
Platform-funded OpenAI access is authorized, but OPENAI_API_KEY is unavailable.
Offline development with the mock LLM proxy
To develop without a provider account, run the repository’s mock model server:
For a JSON Schema it synthesizes a value by walking
const → enum → anyOf/oneOf → object, array, boolean, number, null, picking strings by keyword (thought, summary, title, reason, text/answer, input). Two schema names get hand-written shapes: FactoryChatTurnAnalysis and agent_action.
Point the runtime at it:
MOCK_OPENAI_API_KEY, or the literal mock-openai-key when that is unset — but only when the hostname in OPENAI_BASE_URL is localhost, 127.0.0.1 or ::1. The code states why: keeping the fallback loopback-only stops production credential resolution from silently degrading into process-wide credentials. When the mock key is in use, the key reader short-circuits Postgres and logs a line carrying "source":"local_mock_openai_proxy".
The proxy’s own banner says
MOCK_OPENAI_API_KEY=mock-key while the code’s default is mock-openai-key. The value does not matter — the proxy ignores the bearer token entirely.Symptom, cause, fix
Toolchain and bring-up
Ports, and processes that outlive shutdown
Sync and the database
Sync and migration failures behave the same locally as on a deployment, so they are catalogued once, with their causes, in Postgres, migrations, and the sync publication — a UI that loads with no data, a newly published table with no rows,zero-cache exiting shortly after a schema reset, and relation "<schema>.receipt_job_projection" does not exist during a migration. The checksum refusal you get after editing an already-applied migration is on the same page, under never edit an applied migration.
Locally the blunt fix for the first three is ./bunw run --cwd apps/start zero:reset, which recreates the zero_data publication and deletes zero.db* so the replica is rebuilt from scratch; clear browser site data afterwards, and confirm Postgres is running with wal_level=logical.
Environment values and origins
Model access, sandboxes and integrations
The supervisor itself
Next step: run Receipt on your own infrastructure.