remix-run/remix · error · Error

Migration script declares more than one transaction directiv

Error message

Migration script declares more than one transaction directive

What it means

The migration script parser found two or more transaction directives (e.g. `-- @transaction: ...` style comments) in a single migration file. Because the runner cannot decide which transaction mode to use, it refuses the script at parse time.

Source

Thrown at packages/data-table/src/lib/migrations/directive.ts:31

 */
export function parseTransactionDirective(sql: string): MigrationTransactionMode | undefined {
  let match: MigrationTransactionMode | undefined

  for (let line of sql.split(/\r?\n/)) {
    let trimmed = line.trim()

    if (!trimmed.startsWith('--')) {
      continue
    }

    let result = directivePattern.exec(trimmed)

    if (!result) {
      continue
    }

    if (match !== undefined) {
      throw new Error('Migration script declares more than one transaction directive')
    }

    match = result[1].toLowerCase() as MigrationTransactionMode
  }

  return match
}

View on GitHub (pinned to 9696913134)

Solutions

  1. Open the offending migration file and delete all but one transaction directive line.
  2. If you intended different modes per section, split the migration into separate files since one file maps to one transaction mode.
  3. Re-run the parse/registry load to confirm the file now yields a single mode.

Example fix

// before
-- @transaction: none
-- @transaction: perStep
CREATE TABLE ...

// after
-- @transaction: perStep
CREATE TABLE ...
Defensive patterns

Strategy: validation

Validate before calling

let directivePattern = /^\s*--\s*@transaction:\s*(\w+)/gm
count = (text.match(directivePattern) || []).length
if (count > 1) throw new Error('multiple transaction directives: ' + count)

Type guard

function hasSingleTransactionDirective(text: string): boolean {
  return (text.match(/^\s*--\s*@transaction:/gm) || []).length <= 1
}

Try / catch

try {
  let mode = parseTransactionDirective(script)
} catch (error) {
  if (error instanceof Error && error.message.includes('more than one transaction directive')) {
    // report file path and directive line numbers to the author
  }
  throw error
}

Prevention

When it happens

Trigger: Calling parseTransactionDirective (or the higher-level `directive` parser) on migration script text that contains multiple lines matching the transaction directive pattern, e.g. both `-- @transaction: none` and `-- @transaction: perStep`.

Common situations: Copy-pasting a header block from another migration that already carried a directive; merging migration files during a rebase; adding a new directive without removing the old commented one.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/65860d047c3471b4. Report an issue: GitHub.