Skip to content

Abort startup when database migrations are pending - #375

Open
Deb-Auth wants to merge 5 commits into
ASTROIDX556:mainfrom
Deb-Auth:fix/343-migration-startup-check
Open

Deb-Auth wants to merge 5 commits into
ASTROIDX556:mainfrom
Deb-Auth:fix/343-migration-startup-check

Conversation

@Deb-Auth

Copy link
Copy Markdown
Contributor

Summary

Adds a migration status gate to the boot sequence. It runs before the HTTP server starts listening, so an instance whose database has pending or failed Prisma migrations aborts in production instead of serving traffic against a mismatched schema.

Why

PrismaService.onModuleInit already compared prisma/migrations with _prisma_migrations. However, it only logged the result, and it ran inside a try/catch that swallowed every error. An out-of-date schema therefore never stopped startup, and failures surfaced later as runtime query errors. The checker also treated any query failure (including an unreachable database) as "nothing applied", and it counted rolled-back migrations as applied.

What changed

  • Startup gate: main.ts calls PrismaService.verifyMigrations() right after the logger is set up and before app.listen().
  • Configurable behaviour via the new DATABASE_MIGRATION_CHECK setting:
    • strict, the default when NODE_ENV=production: pending or failed migrations throw PendingMigrationsError, and an unreadable migration history throws MigrationStatusUnavailableError. Either one exits the process with code 1.
    • warn, the default everywhere else: logs the same information and keeps booting, so local development is never blocked.
    • off: skips the check.
  • Clear operator instructions: the log and the error message list every pending or failed migration and the command that fixes it (npm run prisma:deploy, or prisma migrate resolve --rolled-back|--applied <name>).
  • Checker fixes in src/database/migration-checker.ts:
    • Rows with rolled_back_at set are ignored, so rolled-back migrations are reported as pending again, matching prisma migrate deploy.
    • Only a missing _prisma_migrations table (Postgres 42P01) is treated as a fresh database. Any other query error is rethrown instead of being reported as "all pending".
  • DATABASE_MIGRATIONS_DIR: optional override for images that don't keep migrations at <cwd>/prisma/migrations. When no migrations are found on disk, the check logs a warning instead of passing silently.
  • The log-only PrismaService.validateMigrations() is replaced by verifyMigrations(). It had no other callers.
  • Documentation: docs/database.md (new "Startup Migration Check" section) and .env.example.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • CI / tooling

Testing

  • src/database/migration-checker.spec.ts: new unit tests for:
    • mode resolution
    • instruction formatting
    • rolled-back filtering
    • error rethrowing
    • every verifyMigrationsOnStartup path: up to date, pending, failed, database unreachable, off, and no migrations on disk
  • src/database/migration-startup.integration.spec.ts (new): runs a real PrismaService against a real temporary migrations folder, with only the _prisma_migrations query simulated. It covers mock migration states in which startup either proceeds or aborts.

Notes for reviewers

  • NestJS runs other modules' onModuleInit hooks (e.g. queue workers) during NestFactory.create, before this gate runs. The gate guarantees that no HTTP traffic is accepted on a bad schema, and the process then exits. Making non-HTTP components wait for the check would mean moving it into a provider that those modules depend on. I've left that out of scope, but can follow up if you want it.
  • The issue mentions src/index.ts and src/database/migrationChecker.ts. The equivalents in this repo are src/main.ts and the existing src/database/migration-checker.ts, which this PR extends.

Related issue

Closes #343

Checklist

  • npm run build passes
  • npm test passes
  • npm run lint passes
  • npm run typecheck passes
  • No secrets added to tracked files
  • PR description explains the why, not just the what

Run the Prisma migration status check in the boot sequence before the
HTTP server starts listening, and make its outcome configurable via
DATABASE_MIGRATION_CHECK (strict | warn | off). Production defaults to
strict: pending or failed migrations, or an unreadable migration
history, abort startup with remediation steps in the log. Other
environments default to warn so local development is never blocked.

- Ignore rolled-back rows in _prisma_migrations so they count as pending
- Rethrow query failures other than a missing migrations table instead
  of silently treating them as "nothing applied"
- Add DATABASE_MIGRATIONS_DIR to locate migrations in custom layouts
- Replace the log-only PrismaService.validateMigrations with
  verifyMigrations, called from main.ts before app.listen()
- Add unit tests and an integration spec with a real PrismaService and
  on-disk migrations folder
@drips-wave

drips-wave Bot commented Sep 29, 2026

Copy link
Copy Markdown

@Deb-Auth Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

Merge Conflict — Action Needed

This pull request has merge conflicts with the base branch (main).

What to do: update your branch by merging or rebasing against main, resolve any conflicts locally, and push the result.

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

Merge Conflict — Action Needed

This pull request has merge conflicts with the base branch (main).

What to do: update your branch by merging or rebasing against main, resolve any conflicts locally, and push the result.

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

CI fails in migration verification because prisma/migrations contains two folders with the same timestamp prefix 20260928120000 (add_agent_contribution_stats_index and add_notifications_user_created_at_index). Rename one migration with a unique 14-digit timestamp, update any dependent references if needed, and rerun build-and-test.

@Cjay-Cyber-2

Copy link
Copy Markdown
Contributor

CI Checks Failed — Action Needed

The current CI run has failing checks, so this PR remains unmergeable. Please inspect the failed jobs, fix the underlying issues, and push the correction to this PR branch.

Failing checks:

  • build-and-test: FAILURE

I will not merge while these checks are failing.

A bad merge left prisma.service.ts with a duplicate onModuleInit (the
first truncated mid-body, with constructor $extends fragments spliced
into its catch block) and verifyMigrations()'s body merged directly
into validateMigrations()'s declaration with no closing brace — both
unparseable. main.ts had the same issue: two `const prisma` declarations
and both the legacy halt/warn migrationCheckEnabled block and the new
verifyMigrations() call still present.

Restored both migration-check methods since each has its own test
suite requiring it: validateMigrations() (unconditional, run from
onModuleInit via prisma.service.spec.ts) and verifyMigrations() (the
configurable strict/warn/off gate from migration-startup.integration.spec.ts,
called by main.ts). Dropped the superseded migrationCheckEnabled/
migrationCheckMode halt block from main.ts in favor of the single
verifyMigrations() call its own test now covers.

Also documented the two migration-check env vars this surfaced as
missing from docs/configuration.md, and filled in DatabaseConfig fields
the integration spec's fixture was missing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add automated database migration health check on service startup

2 participants