sqlalchemy/alembic · error · ValueError

No such index

Error message

No such index: '%s'

What it means

Raised as ValueError by ApplyBatchImpl.drop_index (batch.py:706-710) after a KeyError when idx.name is not present in self.indexes (the dict of indexes tracked on the batch-recreated table). Batch mode needs to know which index to omit from the new table, so an unknown index name is an error, not a no-op.

Solutions

  1. Confirm the index name via the DB catalog (\di in psql, PRAGMA index_list in SQLite) and pass the exact name.
  2. If you are creating and immediately dropping an index in one batch, simply omit both calls.
  3. If the drop may not apply in all environments, inspect the batch table's indexes before calling drop_index.

Example fix

// before
with op.batch_alter_table('users') as batch_op:
    batch_op.drop_index('ix_user_email')  # typo / wrong name
// after
with op.batch_alter_table('users') as batch_op:
    batch_op.drop_index('ix_users_email')  # correct name
Defensive patterns

Strategy: validation

Validate before calling

# Confirm an index exists before dropping it in batch mode.
def index_exists(conn, table, index_name):
    return conn.exec_driver_sql(
        "SELECT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = %s)",
        (index_name,),
    ).scalar()

if index_exists(bind, 'users', 'ix_users_email'):
    with op.batch_alter_table('users') as batch_op:
        batch_op.drop_index('ix_users_email')

Type guard

def is_known_index(name: str, known: set[str]) -> bool:
    return name in known

Try / catch

try:
    with op.batch_alter_table('users') as batch_op:
        batch_op.drop_index('maybe_missing')
except ValueError as e:
    if 'No such index' in str(e):
        pass  # already absent; idempotent
    else:
        raise

Prevention

When it happens

Trigger: Calling batch_op.drop_index('idx_missing') (or batch_op.drop_index(sa.Index('idx_missing'))) inside a batch_alter_table block where no such index exists on the table. Common with a typo'd index name or an index that belongs to a different table.

Common situations: Auto-generated migration references an index name that was renamed/already dropped; index names that differ between dev (SQLite) and prod (Postgres); dropping an index created in the same batch but with a different name string.

Related errors


AI-assisted analysis of sqlalchemy/alembic@5551b5d35f (2026-08-11). Data as JSON: /api/errors/f9b86f2e7261b72b. Report an issue: GitHub.

Appendix: source

Thrown at alembic/operations/batch.py:710

                # type-bound constraints are only included in the new
                # table via their type object in any case, so ignore the
                # drop_constraint() that comes here via the
                # Operations.implementation_for(alter_column)
                return
            raise ValueError("No such constraint: '%s'" % const.name)
        else:
            if isinstance(const, PrimaryKeyConstraint):
                for col in const.columns:
                    self.columns[col.name].primary_key = False

    def create_index(self, idx: Index) -> None:
        self.new_indexes[idx.name] = idx  # type: ignore[index]

    def drop_index(self, idx: Index) -> None:
        try:
            del self.indexes[idx.name]  # type: ignore[arg-type]
        except KeyError:
            raise ValueError("No such index: '%s'" % idx.name)

    def rename_table(self, *arg, **kw):
        raise NotImplementedError("TODO")

View on GitHub (pinned to 5551b5d35f)