MP053critical

ban-uncommitted-transaction

Migration file contains BEGIN without a matching COMMIT, leaving a dangling open transaction.

operation
Transactions
lock taken
no table lock
remediation
Manual rewrite
category
Lock safety

What triggers it

Only the last statement of the file — ctx.statementIndex !== ctx.allStatements.length - 1 returns null for everything else. On that last statement it counts a depth over every statement in the file, incrementing on isTransactionBegin and decrementing on isTransactionEnd, and reports when the depth ends above zero. Both predicates read TransactionStmt.kind from the parse tree first (TRANS_STMT_BEGIN / TRANS_STMT_START, TRANS_STMT_COMMIT / TRANS_STMT_ROLLBACK), falling back to comment-stripped text only when there is no parse tree.

What does not

Any statement that is not the last one in the file, regardless of what it is. A file whose BEGINs and COMMITs balance. A file with a stray COMMIT and no BEGIN, which drives the depth negative and so never satisfies depth > 0 — the whyItMatters calls that a structural problem but the rule does not report it. SAVEPOINT and RELEASE SAVEPOINT, which are not counted at all.

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

The rule holds no lock of its own and names no lock level. It is about lock duration: whatever the statements inside the block acquired stays acquired, because an uncommitted transaction never releases. That is the queue-risk case — one open transaction holding ACCESS EXCLUSIVE on a hot table parks every subsequent query behind it, indefinitely, until someone kills the session.

Why it matters

A migration with BEGIN but no COMMIT will either fail (if the migration runner auto-commits) or leave an open transaction that holds locks indefinitely. Always match BEGIN with COMMIT or ROLLBACK.

Unsafe, and safe

Flagged

BEGIN;
ALTER TABLE users ADD COLUMN bio text;
-- Missing COMMIT!

Safe alternative

BEGIN;
ALTER TABLE users ADD COLUMN bio text;
COMMIT;

What it assumes

It has no idea what the migration runner does. golang-migrate, Flyway and Rails wrap each file in their own transaction, in which case a bare BEGIN opens a nested block rather than a dangling one and the report is a false positive; a runner that streams the file to psql in autocommit makes it a genuine defect. The rule cannot distinguish the two. Because depth is a simple counter, BEGIN; BEGIN; COMMIT; nets to depth 1 and reports, even though PostgreSQL would have warned on the second BEGIN and treated the COMMIT as closing the one real transaction. The violation is reported at the line of the last statement, not at the unmatched BEGIN.

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.

Entry 02 covers the mechanism: a blocked DDL statement blocks everything behind it.

verified against
PostgreSQL 17.10
last checked
2026-08-11
confidence
High

PostgreSQL manual

What the CLI prints

migrationpilot analyze migration.sql
✗ [MP053] CRITICAL (line 2)
  Migration file contains BEGIN without a matching COMMIT or ROLLBACK. This will leave an open transaction that holds locks indefinitely.

  Safe alternative:
  -- Add COMMIT at the end of the migration:
  COMMIT;

  Why: A migration with BEGIN but no COMMIT will either fail (if the migration runner auto-commits) or leave an open transaction that holds locks indefinitely. Conversely, a COMMIT without a preceding BEGIN is a no-op but indicates a structural problem. Always match BEGIN with COMMIT or ROLLBACK.
  Docs: https://migrationpilot.dev/rules/mp053

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 MP053
BEGIN;

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

.migrationpilotrc.yml
rules:
  MP053: false

# or keep it, and downgrade it
rules:
  MP053:
    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 MP053 in the playground

Related rules