Skip to content

Mark migrations as run

$ grim migrate:mark-applied --all-existing --dry-run --remote

A train may rename a migration file: same table, new date in the name. An instance that took every train never notices. An instance that skipped a few meets the renamed file as a migration it has not run, tries to create a table it already has, and migrate stops with Base table or view already exists. Laravel has no command for the fix, which is a row in the migrations table and nothing else. This is that command.

It never runs a migration. It writes the rows that say one ran, inside the app, with the app's own database configuration, and then a normal migrate carries on with the rest.

Usage

$ grim migrate:mark-applied --all-existing --dry-run            # local container: what would be marked
$ grim migrate:mark-applied --all-existing                      # mark them
$ grim migrate:mark-applied 2026_05_03_000002_create_orders_table   # one by name
$ grim migrate:mark-applied --all-existing --dry-run --remote   # the deployed app, default environment
$ grim migrate:mark-applied --all-existing --remote=staging

Without --remote it talks to the app container of the project in the current directory. With it, to the deployed app, found the way grim remote:artisan finds it: ~/.grim/servers.json on this machine.

Arguments and options

Record migrations as run without running them, for tables that already exist under a renamed migration

Usage

grim migrate:mark-applied [--all-existing] [--dry-run] [--remote [REMOTE]] [--] [<migrations>...]

Arguments

Argument Description
migrations Migration names to mark, with or without .php (optional, repeatable)

Options

Option Description
--all-existing Mark every pending migration whose Schema::create tables already exist
--dry-run Print what would be marked and write nothing
--remote[=REMOTE] Run inside the deployed app instead of the local container; takes the environment name, the project default without one

What it actually does

  1. Builds a script that lists the migration files the app knows — the default directory and every path a module registered — and the names the migrations table already holds.
  2. Picks what to mark. A name you gave is marked when it is pending; one already recorded is skipped, one the app has no file for is an error. With --all-existing every pending migration is read, and the ones whose Schema::create tables all exist already are marked too, with the table names as the reason.
  3. Prints one line per migration, MARK <name> — <reason>, or WOULD MARK in a dry run.
  4. Writes the rows in one new batch, with tinker --execute inside the container, and says how many. Nothing else in the database is touched.

Pitfalls

It only reads Schema::create

A renamed migration that alters a table instead of creating one is not found by --all-existing, and neither is one that creates its table through a raw statement. Read the failing migration, decide, and name it.

Marking a migration that really has not run

The row says it ran; the schema says otherwise, and the next release that depends on the change fails somewhere less obvious. --dry-run first, then compare the list against the error migrate gave you. Mark what it complained about, not what looks similar.

There is no tinker

The script runs through php artisan tinker. An app image that left laravel/tinker out answers Command "tinker" is not defined — add it back to the project before this can work.

With --remote this is production, and the rows are written before you can read the list twice. Run the dry run, read it, then run it again without.

After it finishes

Run the migration that failed again — grim migrate:mark-applied marks, it does not migrate. On a server that is grim deploy once more, or grim remote:artisan migrate --force if you would rather not rebuild anything. Locally, grim artisan migrate.

Recipes that use it

The idea underneath