## TL;DR
The change method can only auto-invert operations rails knows how to reverse; remove_column without a type, change_column, and raw execute are not invertible. Rewrite the migration with explicit up and down methods that spell out both directions, or keep change and wrap just the tricky call in a reversible block. Prove it with db:migrate:redo before merging.

## The query

```text
rails ActiveRecord::IrreversibleMigration - how to write a reversible down migration
```

## Use this when

- rails raises ActiveRecord::IrreversibleMigration on rollback
- db:migrate:redo fails on a migration using change
- A migration calls remove_column without a type or uses execute
- An agent generated a migration that only works in one direction

## Not for

- Migrations that fail while applying (fix the up direction first)
- Data migrations with no schema change (make them idempotent instead)
- Irreversible by design (document it and provide a compensating migration)

## Steps

### 1. Identify the irreversible call

Read the migration and find the operation rails cannot invert. The usual suspects: remove_column without a column type, change_column, change_table with a block rails cannot reverse, and execute with raw SQL.

```bash
rails db:migrate:redo
```

Expected output: IrreversibleMigration naming the migration. The failing operation is the one to isolate.

### 2. Option A: rewrite with explicit up and down

Replace def change with def up and def down, spelling out both directions:

```ruby
def up
  remove_column :users, :legacy_token, :string
end

def down
  add_column :users, :legacy_token, :string
end
```

Expected output: rollback now has explicit instructions. This is the clearest form for anything non-trivial.

### 3. Option B: keep change and wrap the tricky part

```ruby
def change
  reversible do |dir|
    dir.up { execute "[your up SQL]" }
    dir.down { execute "[your down SQL]" }
  end
end
```

Expected output: the rest of change still auto-inverts, and only the wrapped part uses your explicit SQL. Good for one raw statement inside an otherwise reversible migration.

### 4. Prove reversibility before merging

```bash
rails db:migrate:redo
```

Expected output: the migration rolls back and re-applies with no IrreversibleMigration. Run it twice: redo exercises down then up, which is exactly what a deploy rollback would do.

### 5. Add the check to the agent's migration procedure

Every generated migration gets a db:migrate:redo on a scratch database before it is committed. Migrations that cannot roll back never reach the main branch.

Expected output: irreversible migrations fail in development, not during a production rollback at 2am.

## Tool compatibility

rails 6.1 through 8.x with ActiveRecord. reversible and up/down semantics are stable across these versions.

## Variant phrasings

### rails ActiveRecord IrreversibleMigration how to fix

Steps 1 and 2: find the non-invertible call and rewrite with up/down.

### rails change method cannot be reversed

Step 3: wrap the tricky call in reversible so the rest of change keeps auto-inverting.

### rails remove_column irreversible migration

Pass the type: remove_column :users, :legacy_token, :string is reversible, without the type it is not. Step 2 covers the general fix.

## Why it happens

The change method is syntactic sugar: rails records each call and replays its inverse on rollback. Calls with no known inverse (a removed column whose type rails never learned, arbitrary SQL) have no replay, so rails raises instead of guessing. Explicit up/down removes the need to guess.

## Edge cases

- down methods that lose data (re-adding a dropped column comes back empty) are "reversible" but destructive: note the data loss in the migration comment.
- execute with database-specific SQL in down must be tested on the same database family as production; sqlite reversibility proves nothing about postgres.
- Some teams ban change entirely for non-trivial migrations and require up/down always: a fine policy, and it makes this error impossible.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_Bqml9xOjE3fL16XXUhosHg
