remix-run/remix · error · Error

Invalid migration step option. Expected a positive integer.

Error message

Invalid migration step option. Expected a positive integer.

What it means

The `step` option passed to the migration runner was defined but not a positive integer. Steps control how many migrations to apply/rollback, so fractional, zero, negative, or non-integer values are rejected before any work runs.

Source

Thrown at packages/data-table/src/lib/migrations/runner.ts:52

import { resolveMigrations } from './registry.ts'

type MigrationRunnerContext = DatabaseDriver

type RunMigrationsInput = {
  driver: MigrationRunnerContext
  migrations: MigrationDescriptor[]
  journalTable: string
  direction: MigrationDirection
  options: MigrationOperationOptions
}

function assertStepOption(step: number | undefined): void {
  if (step === undefined) {
    return
  }

  if (!Number.isInteger(step) || step < 1) {
    throw new Error('Invalid migration step option. Expected a positive integer.')
  }
}

function assertMigrationOperationOptions(options: MigrationOperationOptions): void {
  if (options.to !== undefined && options.step !== undefined) {
    throw new Error('Cannot combine "to" and "step" migration options in the same run')
  }
}

function resolveTargetOption(
  migrations: MigrationDescriptor[],
  to: string | undefined,
): string | undefined {
  if (to === undefined) {
    return undefined
  }

  // Accept either a bare migration id or the full `id_name` directory form and

View on GitHub (pinned to 9696913134)

Solutions

  1. Pass a positive integer: `step: 2` for two migrations, or omit `step` entirely.
  2. Coerce CLI/env inputs with `Number.parseInt(value, 10)` and validate before calling run.
  3. Use `1` for single-step rollbacks instead of `0` or `-1`.

Example fix

// before
await runMigrations(db, { direction: 'down', step: Number(argv[2]) })

// after
await runMigrations(db, { direction: 'down', step: Math.max(1, Number.parseInt(argv[2], 10) || 1) })
Defensive patterns

Strategy: type-guard

Validate before calling

if (step !== undefined && (!Number.isInteger(step) || step < 1)) {
  throw new Error('--step must be a positive integer')
}

Type guard

function isValidStep(step: unknown): step is number {
  return typeof step === 'number' && Number.isInteger(step) && step >= 1
}

Prevention

When it happens

Trigger: Calling runMigrations with `{ step: 0 }`, `{ step: -1 }`, `{ step: 1.5 }`, or `{ step: '2' }` (string from CLI/env not coerced). assertStepOption is invoked by runMigrationsUnlocked.

Common situations: Passing a raw CLI argument or environment variable straight into options without parsing; computing step as a difference that can be 0 or negative.

Understand the failure class

Background: Invalid option value errors: "must be one of", "is not a valid", and "only allows" failures explained — this error's family across 23 libraries.

Related errors


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