Supabase
Schema, migrations, and verify scripts in packages/supabase/. There is no local DB — the project is linked straight to the remote (prod) via packages/supabase/.temp/project-ref, so every push and write hits production. Migrations are dated SQL files at packages/supabase/migrations/YYYYMMDDHHMMSS_slug.sql.
Apply migrations with bun run push-migrations from the repo root — it filters to @arbe/supabase (so the cwd is right; running supabase db push from the repo root hits the separate edge-functions supabase/ dir and fails the migration-history check) and regenerates database.types.ts afterward. Don’t reach for the raw bunx supabase db push unless you need a flag the wrapper lacks.
bun run push-migrations # apply new migrations to prod + regen types (run from repo root)
cd packages/supabase # for the lower-level commands belowbunx supabase db push --linked --dry-run # preview onlybunx supabase migration list --linked # diff local vs remote historybun run --filter '@arbe/supabase' update-types # regenerate database.types.ts on its own
cd packages/ # workdir auto-resolves here, NOT packages/supabase/bunx supabase db query --linked "select tablename from pg_tables where schemaname='public'" -o table # ad-hoc readbunx supabase db query --linked -f supabase/tests/verify-orphan-house-cleanup.sqlbunx supabase db query --linked -f supabase/tests/verify-cascade-delete-atomicity.sqlExtensions: pg_cron is enabled. Workflow schedules own the wf:<id> jobs — a trigger syncs them from workflows.schedule, so manage them through the column, never cron.schedule directly (see workflows). Inspect with select * from cron.job, firing history in cron.job_run_details.
Verify scripts (packages/supabase/tests/*.sql) are begin; do $$ ... $$; rollback; blocks — DB untouched, asserts raise on first failure. Empty rows + no 400 = passed (the Management API swallows raise notice). A failure surfaces as unexpected status 400: ... ERROR: P0004: FAIL: <message>. psql "$POOLER_URL" -f … also works since scripts are pure SQL.
Gotchas worth remembering:
supabase db queryis not psql — it routes through the Management API, which rejects\set,\echo, or any\-prefixed line withsyntax error at or near "\". Keep verify scripts portable: no meta-commands; any end-of-run banner goes asraise noticeinside a DO block (and accept that the API swallows it).-fis mandatory for files. A bare positional likebunx supabase db query --linked ./tests/foo.sqlis interpreted as inline SQL and fails withsyntax error at or near ".".db query --linkeddoes not run concurrently. Each invocation mints the same temporarycli_login_postgreslogin role (“Initialising login role…”), so two agents querying at the same moment knock each other out withpassword authentication failed for user "cli_login_postgres" (SQLSTATE 28P01). It reads exactly like dead credentials and is not — retry once or twice before calling it a blocker, and only believe it when a read fails alone and repeatedly. Worth telling delegated workers explicitly; a fresh worker treats the first 28P01 as a hard stop.- Workdir auto-detection lands on
packages/, notpackages/supabase/. Paths passed to-fare resolved relative to that workdir, so from anywhere inside the supabase package you still writesupabase/tests/foo.sql. - A new function is executable by everyone until you revoke it. Postgres grants
EXECUTEtoPUBLICon create, and PostgREST exposes everypublic-schema function as an RPC — sogrant execute ... to service_rolealone narrows nothing;anonstill has it viaPUBLIC. Anysecurity definerfunction needs an explicitrevoke all on function ... from public, anon, authenticatedfollowed by agrantto the roles that may call it (the revoke stripsservice_roletoo whenPUBLICwas its only grant). This is howresolve_secrets_for_scopestayed anon-callable. Guard:bunx supabase db query --linked -f supabase/tests/verify-definer-grants.sql. - RLS on with no policies is deny-all, but only for rows. That is the right shape for service-role-only tables such as
usage_events,wf_conductors,wf_run_threads,thread_directors, andfeedback. Revoke table grants too:TRUNCATEis a table-level privilege that no policy can filter. The same verify script covers both layers. - Rewriting a function needs a new migration. Always rebuild from the latest definition, not the earliest:
rg -l "function_name" supabase/migrations/ | tailand start from the newest migration’s version — recreating from an older one silently drops parameters/columns added later (this broke the feedback RPC when a rate-limit edit restored a pre-sentiment signature). To changecleanup_orphaned_housesor anycreate or replace function, add a new dated migration that re-issues the statement — don’t edit historical migration files (e.g.20260417000000_retire_stranded_bots.sqllayered on top of earlier RLS work). - Connection pooler vs direct.
supabase db pushuses the pooler URL (port 5432,pooler.supabase.com). Directdb.<project>.supabase.co:5432works too but isn’t what the CLI advertises. - Never edit
database.types.tsby hand — it’s a generated artifact. Manual edits drift from the remote schema and silently break adapters that trust the type. - A jsonb parameter bound to the string
"null"becomes jsonb null, not SQL NULL, and failscol is nullchecks. Bind JSnullfor SQL NULL; stringify only actual objects. Cannot find project ref?--linkedreads the ref only frompackages/supabase/.temp/project-ref(gitignored, not committed) — not fromconfig.tomlproject_idnor$SUPABASE_PROJECT_REF. On a fresh clone, relink once from inside the package:cd packages/supabase && bunx supabase link --project-ref gxlrglyxsrldjjrpkdsy. And run query/migration commands from insidepackages/— the repo-rootsupabase/is a separate dir (edge functions) whose--linkedlookup fails the same way.
Code: packages/supabase/migrations/, packages/supabase/tests/, packages/supabase/database.types.ts.
See system/storage, system/permissions.