Skip to content

Apply schema changes to production during the build - #5

Merged
7174Andy merged 1 commit into
mainfrom
fix-prod-schema-drift
Jul 29, 2026
Merged

Apply schema changes to production during the build#5
7174Andy merged 1 commit into
mainfrom
fix-prod-schema-drift

Conversation

@7174Andy

Copy link
Copy Markdown
Owner

The outage this closes

Schedule.runId, runUrl, and runConclusion were added in 9348c93, merged, and deployed. The production database never got the columns, so the deployed Prisma Client kept querying columns that did not exist:

The column `Schedule.runUrl` does not exist in the current database.
The column `Schedule.runConclusion` does not exist in the current database.

It was never applied because nothing could have applied it. There was no prisma/migrations/, build was prisma generate && next build (generates a client, never connects), and scripts/prisma.mjs refuses to run against production by design. Every prior schema change was applied to prod by hand; this one was missed and nothing anywhere reported it.

Impact, traced per call site:

Path
createSchedule / getSchedules broken — the user-visible failure
Run-resolution pass broken — errored on every cron tick for five days
Workflow dispatch unaffected

Dispatch survived because getDueScheduleIds, claimSchedule, and updateScheduleStatus touch only pre-existing columns, and resolveTriggeredSchedules is isolated in its own catch. Workflows kept firing, which is why this stayed quiet.

Already done, outside this PR

The three columns have been applied to production by hand, so production is already working — this PR changes nothing about the current database state. 0_init is marked applied there, so migrate deploy is a verified no-op rather than an attempt to CREATE TABLE over live rows.

What this changes

  • prisma/migrations/ becomes the source of truth, baselined at 0_init from the current schema.
  • scripts/migrate-deploy.mjs runs prisma migrate deploy in the build, gated on VERCEL_ENV=production.
  • build becomes prisma generate && node scripts/migrate-deploy.mjs && next build.

Two deliberate choices, both reversible:

Previews skip migration. A preview build pointed at the production database would apply an unmerged branch's migrations to production. I could not verify how preview DATABASE_URL is configured, so I assumed the unsafe case. If previews have their own database, drop the gate so they migrate too.

A production build with no DATABASE_URL fails rather than skipping. Skipping would restore exactly the silence this removes. Failing is the safe direction — Vercel keeps serving the previous deployment.

⚠️ Check before merging

DATABASE_URL must be exposed to Vercel's Build step, not only the runtime. If it is currently runtime-only, the first production deploy after this merges will fail. Safely — the current deployment keeps serving — but it will block until the setting changes.

Verification

  • 0_init replayed from empty against a shadow database diffs clean against schema.prisma
  • prisma migrate diff against production: no drift
  • migrate deploy against production: No pending migrations to apply
  • The previously-failing select clauses re-run against production: pass
  • Gate: skips locally (no VERCEL_ENV), skips on preview, exits 1 on production without DATABASE_URL
  • Real project tests 49/49, tsc --noEmit clean, eslint clean on source, npm run build succeeds

Unrelated to this PR: npm test and npm run lint at repo root also pick up stale .claude/worktrees/ copies (17 failing files, ~9984 lint problems). Pre-existing on main; the ignore rules that fix it are on dev-oauth-app-setup.

Not included

The db:deploy / db:migrate / db:status scripts and the README documentation depend on scripts/prisma.mjs, which lands with dev-oauth-app-setup. They ride along with that branch so this one stays mergeable on its own.

🤖 Generated with Claude Code

Nothing applied schema changes to production. `prisma generate` builds a
client that assumes the schema but never connects, so a merged schema change
deployed a client whose queries referenced columns the database did not have.
The failure surfaced only at runtime, and only in the logs.

That is how `Schedule.runId`, `runUrl`, and `runConclusion` -- added in
9348c93 and deployed -- reached production missing. Creating and listing
schedules failed outright, and the run-resolution pass errored on every cron
tick for five days. Dispatch itself kept working: it touches only
pre-existing columns, and resolveTriggeredSchedules is isolated in its own
catch, so the workflows fired and nothing surfaced the breakage.

The columns have since been applied by hand, so this changes nothing about
the current database. What it changes is that the next one cannot be missed.

prisma/migrations/ becomes the source of truth, baselined at 0_init from the
current schema and marked applied on production, so `migrate deploy` is a
no-op against it rather than an attempt to CREATE TABLE over live rows.

The build step is gated on VERCEL_ENV. A preview build pointed at the
production database would otherwise apply an unmerged branch's migrations to
production; if preview deployments get their own database, drop the gate so
previews migrate too. A production build with no DATABASE_URL fails rather
than skipping -- skipping would restore exactly the silence this removes, and
failing is the safe direction, since Vercel keeps serving the previous
deployment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gitcron Ready Ready Preview, Comment Jul 29, 2026 6:27pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant