Knex takes a row in knex_migrations_lock so two migration runs cannot proceed at once. If a previous run was killed before releasing it, the lock stays set and the next run reports "already locked" - or two runs genuinely overlap.
What this error means
knex migrate:latest fails with "Migration table is already locked", often after a cancelled or OOM-killed CI job, or when two jobs migrate the same database concurrently.
psql
Knex: Error
Migration table is already locked
MigrationLocked: Migration table is already locked
Common causes
Stale lock from a killed run
A cancelled, timed-out, or OOM-killed run never released the lock, so knex_migrations_lock stays locked and blocks the next run.
Concurrent migration runs
Two pipelines migrating the same database at once legitimately contend for the lock.
How to fix it
Release a stale lock
When no other migration is running, unlock so the next run can proceed.
Terminal
npx knex migrate:unlock
npx knex migrate:latest
Serialize migrations
Use a CI concurrency group so only one migration job runs against a database at a time.
Avoid two pipelines targeting the same database concurrently.
Set a step timeout so a stuck lock surfaces fast.
How to prevent it
Constrain migration jobs with a concurrency group so they cannot overlap.
Clear stale locks at the start of a controlled, single-writer migration.
On managed runners (Latchkey), self-healing auto-retries transient lock-contention failures so a briefly-held lock can clear.
Frequently asked questions
What causes ""Migration table is already locked""?
A cancelled, timed-out, or OOM-killed run never released the lock, so knex_migrations_lock stays locked and blocks the next run.
How do I fix "Migration table is already locked"?
When no other migration is running, unlock so the next run can proceed.
Can Latchkey fix this automatically?
Yes. Latchkey runs your GitHub Actions on managed runners that detect this failure, apply the fix, and retry the job automatically - self-healing is on by default.