migrations/migrate.php (~lines 118-160) wraps each migration file's statements in begin_transaction()/rollback(), but MySQL DDL statements (ALTER TABLE, CREATE TABLE, etc.) cause an implicit commit — so a rollback does not undo earlier DDL statements already executed within that same file if a later statement in the file fails.
Impact: A migration that fails partway leaves the DB partially altered and unrecorded in the migrations table (the INSERT INTO migrations only runs after all statements succeed). The next run then retries the whole file from statement 1 — including the already-applied DDL — which can now fail with "column already exists"/"constraint already exists" style errors that aren't on the safe-to-ignore allowlist (only Duplicate key name/index-already exists is currently allowlisted). This can wedge the runner in a permanently-failing state requiring manual DB surgery.
Fix: Either split multi-statement migrations so each DDL statement is individually idempotent/safe-to-retry (matching the 000_baseline.sql philosophy), or track applied statements at finer granularity than whole-file, or explicitly document that migrations must be written idempotently since transactional rollback can't be relied on for DDL.
**Severity:** High
`migrations/migrate.php` (~lines 118-160) wraps each migration file's statements in `begin_transaction()`/`rollback()`, but MySQL DDL statements (`ALTER TABLE`, `CREATE TABLE`, etc.) cause an implicit commit — so a rollback does **not** undo earlier DDL statements already executed within that same file if a later statement in the file fails.
**Impact:** A migration that fails partway leaves the DB partially altered *and* unrecorded in the `migrations` table (the `INSERT INTO migrations` only runs after all statements succeed). The next run then retries the whole file from statement 1 — including the already-applied DDL — which can now fail with "column already exists"/"constraint already exists" style errors that aren't on the safe-to-ignore allowlist (only `Duplicate key name`/index-`already exists` is currently allowlisted). This can wedge the runner in a permanently-failing state requiring manual DB surgery.
**Fix:** Either split multi-statement migrations so each DDL statement is individually idempotent/safe-to-retry (matching the `000_baseline.sql` philosophy), or track applied statements at finer granularity than whole-file, or explicitly document that migrations must be written idempotently since transactional rollback can't be relied on for DDL.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Severity: High
migrations/migrate.php(~lines 118-160) wraps each migration file's statements inbegin_transaction()/rollback(), but MySQL DDL statements (ALTER TABLE,CREATE TABLE, etc.) cause an implicit commit — so a rollback does not undo earlier DDL statements already executed within that same file if a later statement in the file fails.Impact: A migration that fails partway leaves the DB partially altered and unrecorded in the
migrationstable (theINSERT INTO migrationsonly runs after all statements succeed). The next run then retries the whole file from statement 1 — including the already-applied DDL — which can now fail with "column already exists"/"constraint already exists" style errors that aren't on the safe-to-ignore allowlist (onlyDuplicate key name/index-already existsis currently allowlisted). This can wedge the runner in a permanently-failing state requiring manual DB surgery.Fix: Either split multi-statement migrations so each DDL statement is individually idempotent/safe-to-retry (matching the
000_baseline.sqlphilosophy), or track applied statements at finer granularity than whole-file, or explicitly document that migrations must be written idempotently since transactional rollback can't be relied on for DDL.Fixed and verified against real MariaDB (schema/migration/access-control cases). Merged to main in commit
3664719.