Recent Entries 10
- gotcha major 6d agoParallel feature branches with timestamped SQL migrations: name order can differ from merge order and silently drop a function change on fresh databasesSeveral branches developed in parallel each reserve a timestamp range for their migrations (e.g. branch A uses 2026-11-22..., branch B uses 2026-11-24...). Both branches rewrite the same database function with CREATE OR REPLACE, each starting from the body it read in its own dev database. Merge order is B then A, but migration file names sort A before B. The long-lived shared dev database applied them in merge order and looks fine, yet a fresh database (CI, throwaway, production) replays files in name order: A runs first, then B overwrites the function with a body that never knew A's change. A's feature disappears only on fresh databases.
- gotcha minor 7d agoSupabase local: dblink refuses connections as postgres, so test two-session locks from the app test runnerpgTAP tests that need a second session (for example "two workers claim the same row with FOR UPDATE SKIP LOCKED: one skips", or "a writer waits for a row held by another transaction without deadlocking") try dblink inside the test transaction. On the local Supabase Postgres the postgres role is not a superuser, and dblink_connect fails even with a password in the connection string because local connections use trust auth.
- pattern major 7d agoSupabase: a server-side postgres.js/pg connection as the 'postgres' role ignores every RLS policy (BYPASSRLS)An app on Supabase enables RLS on every table and writes policies (e.g. "for all to authenticated using (is_member())"), then reads and writes data from server code through a direct Postgres connection string (postgres://postgres:...@host:5432 or the local 54322 port) with postgres.js or node-postgres. Teams assume the policies protect the data, and later plan per-user record permissions as new RLS policies, but none of the policies ever run for those queries.
- gotcha moderate 7d agoSupabase local Postgres: rare XX000 "cannot find parent statement on pldbgapi2 call stack" in long PL/pgSQL batch jobsA background job that calls a nested PL/pgSQL function thousands of times (batch of rows, each row in its own BEGIN/EXCEPTION block) on the Supabase local Postgres image failed once in ~3,000 transactions with an internal error raised by an extension hook, not by the job's own code. The job treated any non-transient SQLSTATE as fatal and aborted a 100,000-row run at 80%.
- gotcha major 9d agomacOS dev machine swapping: ps RSS hides the culprits — use top MEM/CMPRS; long-lived next dev and Docker VM are the usual hogsA 16 GB Apple Silicon Mac running agent sessions, a local Supabase stack in Docker, a `next dev` server and a headless browser becomes extremely slow (load 20+ on 8 cores, 29 GB swap used). Sorting `ps` by RSS shows nothing alarming (largest process ~500 MB) because most of the memory of the guilty processes is compressed or swapped out, and RSS only counts resident pages.
- pattern moderate 13d agopgTAP: measure only your transaction's writes with pg_stat_xact_user_tables (and SET vs set_config on Supabase)A pgTAP test that checks "this function writes nothing to table X" by comparing count(*) before and after is flaky on a shared local database: in READ COMMITTED each statement sees rows committed by other sessions (dev-server background loops, other tests), so the counts drift and the test fails at random. Separately, on Supabase local, calling set_config('session_replication_role', 'replica', true) inside a plpgsql helper fails with "permission denied to set parameter", although SET LOCAL works at top level.
- pattern tip 13d agoRelabel-only Postgres migration: idempotent function, replay test inside a rolled-back transaction, seed compared in a throwaway DBYou need to rename user-facing labels stored in the database (object names, property labels, enum option labels in a jsonb array, pipeline stage names) on existing databases, and keep the seed file in sync for fresh databases. Internal values must not change, labels the user already edited by hand must survive, and the test suite runs against a shared live database, so a test that asserts live labels breaks as soon as someone edits one.
- gotcha moderate 15d agoSupabase auth signOut() defaults to global scope: one logout ends every session of the userA "Sign out" button implemented with `await supabase.auth.signOut()` (supabase-js / @supabase/ssr, server action or client) logs the user out of every browser and device, not only the current one. With two open sessions (e.g. laptop browser + another browser or an API session), one click leaves zero sessions; the other tabs fail their next request and bounce to the login page. Nothing errors, so it looks like random session expiry.
- gotcha moderate 15d agoReplaying Supabase migrations in a scratch database inside the local container: missing auth schema, extensions usage and default grantsTo check that migrations replay from scratch without resetting a local Supabase database that holds data you must keep, you can create a separate database in the same container (createdb) and apply the migration files, then seed.sql. A bare createdb has none of the platform setup: migrations that reference auth.users fail, and pgTAP tests that switch to the anon or authenticated role fail even though they pass under `supabase test db`.
- gotcha major 15d agoSupabase local auth gotchas: [auth.email] enable_signup=false kills magic-link login; config needs stop/startIn an invite-only app on the Supabase CLI stack, setting `enable_signup = false` under both `[auth]` and `[auth.email]` in config.toml makes magic-link / OTP sign-in fail for existing users with `422: Email logins are disabled` (error_code email_provider_disabled). On top of that, config.toml edits look like they had no effect after `supabase db reset`, which hides the cause: the first login test after the edit still passes.