Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions .github/actions/check-prisma-migrations/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Check Prisma Migrations
description: >-
Verify Prisma migrations apply cleanly in order and reproduce schema.prisma
(drift check), and that no new migration predates the latest on the base
branch (ordering check). Designed to be embedded in an existing job so its
failure turns that job's status red.

inputs:
base-ref:
description: >-
Base git ref to diff migrations against (e.g. a PR's base branch). When
set, the action skips work on PRs that don't touch migrations and runs the
ordering check. When empty (release builds), the drift check always runs
and the ordering check is skipped.
required: false
default: ""

runs:
using: composite
steps:
- name: Detect Prisma changes
id: detect
shell: bash
run: |
if [ -z "${{ inputs.base-ref }}" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "No base-ref provided — running drift check unconditionally."
exit 0
fi
git fetch --no-tags --depth=1 origin "+refs/heads/${{ inputs.base-ref }}:refs/remotes/origin/${{ inputs.base-ref }}"
if git diff --name-only "origin/${{ inputs.base-ref }}" HEAD | grep -q '^packages/db/prisma/'; then
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "Prisma changes detected — running migration checks."
else
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No Prisma changes — skipping migration checks."
fi

- name: Start Postgres
if: steps.detect.outputs.changed == 'true'
shell: bash
run: |
docker run -d --name prisma-check-pg \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=sourcebot \
-p 5432:5432 postgres:16
for i in $(seq 1 30); do
if docker exec prisma-check-pg pg_isready -U postgres -q; then
echo "Postgres ready."
exit 0
fi
sleep 2
done
echo "Postgres failed to become ready." && exit 1

- name: Use Node.js
if: steps.detect.outputs.changed == 'true'
uses: actions/setup-node@v4
with:
node-version: "20.x"

- name: Install
if: steps.detect.outputs.changed == 'true'
shell: bash
run: yarn install --frozen-lockfile

# Check 1: migrations apply cleanly in order AND reproduce schema.prisma.
# `migrate deploy` fails if a migration is broken or applies out of sequence;
# `migrate diff` exits 2 when the applied history drifts from the schema.
- name: Apply migrations
if: steps.detect.outputs.changed == 'true'
shell: bash
working-directory: packages/db
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/sourcebot
run: yarn prisma migrate deploy

- name: Check for schema drift
if: steps.detect.outputs.changed == 'true'
shell: bash
working-directory: packages/db
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/sourcebot
run: |
yarn prisma migrate diff \
--from-database \
--to-schema-datamodel prisma/schema.prisma \
--exit-code \
&& echo "✅ No drift: migrations reproduce schema.prisma" \
|| (echo "❌ schema.prisma has changes not captured in a migration. Run: yarn dev:prisma:migrate:dev --name <name>" && exit 1)

# Check 2 (PRs only): no new migration predates the latest on the base branch.
- name: Check migration ordering
if: steps.detect.outputs.changed == 'true' && inputs.base-ref != ''
shell: bash
run: |
MIG_DIR=packages/db/prisma/migrations
BASE="origin/${{ inputs.base-ref }}"
LATEST_ON_BASE=$(git ls-tree -r --name-only "$BASE" -- "$MIG_DIR" \
| sed -n "s#$MIG_DIR/\([0-9]\{14\}\)_.*#\1#p" | sort | tail -1)
echo "Latest migration on ${{ inputs.base-ref }}: ${LATEST_ON_BASE:-<none>}"
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."
Comment on lines +103 to +109

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

FAIL=1
fi
done
[ "$FAIL" -eq 0 ] && echo "✅ Migration ordering OK"
exit $FAIL
8 changes: 8 additions & 0 deletions .github/workflows/_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ jobs:
fetch-depth: 0
token: ${{ inputs.use_app_token && steps.generate_token.outputs.token || github.token }}

# Release backstop: fail the build if migrations drift from schema.prisma.
# Runs once (amd64 only) since the check is platform-independent. base-ref
# is omitted, so the drift check always runs and the (PR-only) ordering
# check is skipped.
- name: Check Prisma migrations
if: matrix.platform == 'linux/amd64'
uses: ./.github/actions/check-prisma-migrations

# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
Expand Down
12 changes: 11 additions & 1 deletion .github/workflows/pr-gate.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: PR Gate

# This gate simply validates that we can build the docker container.
# This gate validates that Prisma migrations are in order and that we can build
# the docker container.

on:
pull_request:
Expand All @@ -16,6 +17,15 @@ jobs:
uses: actions/checkout@v4
with:
submodules: "true"
# full history so migration checks can diff against the base branch
fetch-depth: 0

# Fails fast (before the docker build) when migrations drift from
# schema.prisma or a new migration is added out of timestamp order.
- name: Check Prisma migrations
uses: ./.github/actions/check-prisma-migrations
with:
base-ref: ${{ github.base_ref }}

- name: Build Docker image
id: build
Expand Down
Loading