ci: enforce prisma migration ordering and schema drift#1400
Conversation
Add a GitHub Actions workflow that runs on PRs touching packages/db/prisma/**. It verifies migrations apply cleanly in sequence and reproduce schema.prisma (drift check via prisma migrate diff), and that no newly added migration predates the latest migration on main (out-of-order timestamp check). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
WalkthroughA new composite GitHub Action ( ChangesPrisma Migrations CI Enforcement
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/prisma-migrations.yml:
- Around line 31-42: Disable persisted checkout credentials before the Install
step in the prisma-migrations workflow so install-time scripts cannot access the
GitHub token. Update the actions/checkout@v4 configuration to avoid leaving
credentials in .git/config, and keep the existing checkout setup for submodules
and fetch-depth while preserving the later yarn install --frozen-lockfile step.
- Around line 44-58: The current prisma-migrations workflow only checks that the
final schema matches, so it can miss edits to existing applied migration files.
Update the “Apply migrations” / “Check for schema drift” flow in the
prisma-migrations job to also detect rewrites of files under
packages/db/prisma/migrations by comparing the migration directory against main
or by verifying migration checksums/history, so any modification to an existing
timestamped migration fails even if the end schema is unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: bd8dea3e-b511-4e8b-b9ff-2f0f52347a2a
📒 Files selected for processing (1)
.github/workflows/prisma-migrations.yml
Replace the standalone migration workflow with a composite action (.github/actions/check-prisma-migrations) and embed it in: - pr-gate.yml's build job, so migration drift or out-of-order timestamps fail the required PR check before merge. - _build.yml's build job (amd64 only) as a release-time backstop for schema drift. The action self-contains Postgres via docker run, detects whether Prisma files changed (skipping fast on PRs that don't), verifies migrations apply in sequence and reproduce schema.prisma, and checks no new migration predates the latest on the base branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/actions/check-prisma-migrations/action.yml:
- Around line 103-109: The migration check in check-prisma-migrations/action.yml
is collapsing directory names to bare timestamps before comparing new migrations
against the base branch, which can hide same-timestamp migrations with different
suffixes. Update the NEW detection logic in the shell block to compare full
migration directory names first, using the existing MIG_DIR and LATEST_ON_BASE
flow, then extract timestamps only for the age check. Keep the final guard in
the same loop, but reject any new migration whose timestamp is less than or
equal to LATEST_ON_BASE so timestamp-colliding directories are caught.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ff6b4629-0677-4ecf-bce0-ea5ea66cdd7f
📒 Files selected for processing (3)
.github/actions/check-prisma-migrations/action.yml.github/workflows/_build.yml.github/workflows/pr-gate.yml
| NEW=$(comm -23 \ | ||
| <(ls "$MIG_DIR" | sed -n 's/^\([0-9]\{14\}\)_.*/\1/p' | sort -u) \ | ||
| <(git ls-tree -r --name-only "$BASE" -- "$MIG_DIR" | sed -n "s#$MIG_DIR/\([0-9]\{14\}\)_.*#\1#p" | sort -u)) | ||
| FAIL=0 | ||
| for ts in $NEW; do | ||
| if [ -n "$LATEST_ON_BASE" ] && [ "$ts" -lt "$LATEST_ON_BASE" ]; then | ||
| echo "❌ New migration $ts predates latest migration on ${{ inputs.base-ref }} ($LATEST_ON_BASE). Rename it with a current timestamp." |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Don't collapse new migrations to timestamps.
Line 103 drops the directory suffix before comm, so a PR migration like 20260629010101_add_index is invisible if the base branch already has a different 20260629010101_* directory. That skips the exact merge-race this guard is supposed to catch. Diff full directory names first, then reject any new migration whose timestamp is <= LATEST_ON_BASE.
Suggested fix
- NEW=$(comm -23 \
- <(ls "$MIG_DIR" | sed -n 's/^\([0-9]\{14\}\)_.*/\1/p' | sort -u) \
- <(git ls-tree -r --name-only "$BASE" -- "$MIG_DIR" | sed -n "s#$MIG_DIR/\([0-9]\{14\}\)_.*#\1#p" | sort -u))
+ NEW=$(comm -23 \
+ <(find "$MIG_DIR" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort) \
+ <(git ls-tree -d --name-only "$BASE" -- "$MIG_DIR" | sed "s#^$MIG_DIR/##" | sort))
FAIL=0
- for ts in $NEW; do
- if [ -n "$LATEST_ON_BASE" ] && [ "$ts" -lt "$LATEST_ON_BASE" ]; then
- echo "❌ New migration $ts predates latest migration on ${{ inputs.base-ref }} ($LATEST_ON_BASE). Rename it with a current timestamp."
+ for migration in $NEW; do
+ ts=${migration%%_*}
+ if [ -n "$LATEST_ON_BASE" ] && [ "$ts" -le "$LATEST_ON_BASE" ]; then
+ echo "❌ New migration $migration does not sort after latest migration on ${{ inputs.base-ref }} ($LATEST_ON_BASE). Rename it with a newer timestamp."
FAIL=1
fi
done🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/actions/check-prisma-migrations/action.yml around lines 103 - 109,
The migration check in check-prisma-migrations/action.yml is collapsing
directory names to bare timestamps before comparing new migrations against the
base branch, which can hide same-timestamp migrations with different suffixes.
Update the NEW detection logic in the shell block to compare full migration
directory names first, using the existing MIG_DIR and LATEST_ON_BASE flow, then
extract timestamps only for the age check. Keep the final guard in the same
loop, but reject any new migration whose timestamp is less than or equal to
LATEST_ON_BASE so timestamp-colliding directories are caught.
Summary
Enforces that Prisma migrations are always in order, gating both PRs and release builds.
The logic lives in a reusable composite action,
.github/actions/check-prisma-migrations, embedded in two existing build jobs:pr-gate.yml(buildjob — the required PR check): fails the PR before merge if migrations are out of order. Runs early, before the Docker build, to fail fast._build.yml(buildjob, amd64 only): release-time backstop. Failsrelease-dev/release-prodif schema drift slips through.Checks
prisma migrate deploy(fails if migrations are broken or apply out of sequence), thenprisma migrate diff --from-database --to-schema-datamodel --exit-codeto confirm the applied history reproducesschema.prisma. Catches schema edits with no migration and hand-edited migrations.Notes
docker run(job-levelservices:can't be shared across workflows) and detects whetherpackages/db/prisma/**changed, skipping fast on PRs that don't touch migrations — so the requiredbuildcheck always reports and never sits pending.Summary by CodeRabbit