db-change
Change the database schema safely (new table or column, index, RLS policy, grants, backfill, drop). Use when a feature needs to store something new or change how it's stored, or when the user mentions
SKILL.md.claude/skills/db-change/SKILL.md
GitHubChange the database
- Edit the owning schema's file in
packages/db/prisma/schema/(app.prismafor product data;docs/database.mdlists the owners). Followbase.prisma:uuidv7()ids,timestamptz(3),@map/@@mapto snake_case,@@schemaon every model. A tenant table hasorgIdwith anorganizationrelationonDelete: Cascadeand an index on[orgId, id(sort: Desc)], likeTodoinapp.prisma. bun run db:migrate. Prisma asks for a name, writespackages/db/prisma/migrations/<timestamp>_<name>/migration.sqland applies it.- Prisma can't express policies, grants or functions: append them to that
migration.sqlby hand. A new tenant table needs, in the same migration:
Tables inALTER TABLE app.<table> ENABLE ROW LEVEL SECURITY; ALTER TABLE app.<table> FORCE ROW LEVEL SECURITY; CREATE POLICY tenant_isolation ON app.<table> USING (org_id = nullif(current_setting('app.org_id', true), '')::uuid) WITH CHECK (org_id = nullif(current_setting('app.org_id', true), '')::uuid);appandauthgetapp_api's grants automatically (default privileges in the init migration); any other role or schema needs an explicitGRANT, as narrow as the service needs (see thefilesandwebhooksmigrations). Per-user tables useuser_idandapp.user_id(withUser/userTx). Editing a migration Prisma already applied makes the nextbun run db:migrateask to reset the local database, which deletes local data: ask the user first. bun run db:lint(Squawk). Fix what it flags; a statement that is safe in context gets-- squawk-ignore <rule>above it with the reason.bun run gen(Prisma client). Foraitables, also updateapps/ai/app/db/models.py.- Tests:
bun run test:integrationconnects as the service roles, so a missing grant or policy fails there. The Python models are checked byapps/ai/tests/test_models_match_db.py.
Expand, then contract
Migrations run before the new code, and the old release keeps serving during the rollout and after a rollback. Every migration must work with both:
- Add the column or table (nullable, or with a default). Never rename or change a type in place.
- Backfill in batches, with
SET lock_timeout. - Ship the code that reads and writes the new shape.
- Drop the old column in a later release, once nothing running uses it.
Never edit a migration that is on master; write a new one.
Finish
- The verify skill (it runs
db:lint, the drift check and the integration tests for a migration). - The
migration-revieweragent on the migration, and fix what it reports. Then therevieweragent on the code that uses the new shape.