MP004criticalauto-fix

require-lock-timeout

DDL operations should set lock_timeout to prevent blocking the lock queue indefinitely.

operation
Session settings
lock taken
ACCESS EXCLUSIVE
remediation
Fixed by --fix
category
Lock safety

What triggers it

Fires on any DDL statement whose computed lock type is ACCESS EXCLUSIVE or SHARE when no preceding statement in the migration set lock_timeout — checked both via a VariableSetStmt named lock_timeout and, as a fallback, by searching the raw SQL text of earlier statements.

What does not

Skips statements that don't take ACCESS EXCLUSIVE or SHARE, SET/RESET/SHOW/transaction-control statements themselves (VariableSetStmt, VariableShowStmt, TransactionStmt), and plain CREATE TABLE — a new table has no contention to wait on. It also stands down once any earlier statement in the file already set lock_timeout.

Where it applies

Applies to every PostgreSQL version MigrationPilot targets. It works on the SQL text alone — no database connection needed.

The lock, and what it blocks

This rule isn't really about the lock the DDL takes — it's about what happens while it waits to get it: without lock_timeout, a blocked DDL statement queues indefinitely behind whatever holds the lock, and every later query on that table queues behind the DDL in turn.

Why it matters

Without lock_timeout, if the table is locked by another query, your DDL waits indefinitely. All subsequent queries pile up behind it in the lock queue, causing cascading timeouts across your application.

Unsafe, and safe

Flagged

ALTER TABLE users ADD COLUMN bio TEXT;

Safe alternative

SET lock_timeout = '5s';
ALTER TABLE users ADD COLUMN bio TEXT;
RESET lock_timeout;

What it assumes

The lock_timeout lookback only checks earlier statements in the same migration file — a lock_timeout set at the role or database level, or by a wrapper script before the migration runs, won't be seen, so the rule can flag a statement that's already covered outside the file it's analyzing.

What backs this rule

Every rule is a claim about PostgreSQL, so it carries what the claim rests on: a handbook chapter that cites the manual, the incidents that put it there, and the version it was last checked against.

What the CLI prints

migrationpilot analyze migration.sql
✗ [MP004] CRITICAL (line 1)
  DDL statement acquires ACCESS EXCLUSIVE lock without a preceding SET lock_timeout. Without a timeout, this statement could block the lock queue indefinitely if it can't acquire the lock, causing cascading query failures.

  Safe alternative:
  -- Set a timeout so DDL fails fast instead of blocking the queue
  SET lock_timeout = '5s';
  ALTER TABLE users ADD COLUMN bio TEXT
  RESET lock_timeout;

  Why: Without lock_timeout, if the table is locked by another query, your DDL waits indefinitely. All subsequent queries pile up behind it in the lock queue, causing cascading timeouts across your application. GoCardless enforces a 750ms lock_timeout for this reason.
  Docs: https://migrationpilot.dev/rules/mp004

Generated by running the CLI's own formatter over the flagged example above, so it is the text the tool actually produces. A real run also reports the other rules that fire on the same statement; those blocks are left out here.

Turning it off

For one statement, put a comment on the line before it:

-- migrationpilot-disable MP004
ALTER TABLE users ADD COLUMN bio TEXT;

For the whole project, in .migrationpilotrc.yml — by name or by id:

.migrationpilotrc.yml
rules:
  MP004: false

# or keep it, and downgrade it
rules:
  MP004:
    severity: warning

Try it

Open this rule's flagged example in the playground. It runs in your browser — edit it and watch the finding appear and disappear.

Run MP004 in the playground

Related rules