Troubleshooting
Common problems and how to fix them. Each entry names the error you'll see, why it happens, and what to do. For the full list of error codes, see the Error Codes reference.
"Lock already held"
LockAlreadyHeldError: Migration lock is held by pid 48213 on host deploy-runner-7Why: another migronaut process is running migrations, or a previous run crashed and left the lock behind.
Fix:
- If a migration really is running elsewhere, wait — this is the lock doing its job.
- If you're sure nothing is running (e.g. a CI job was killed), clear it:bash
migronaut unlock - The lock also auto-expires after
lockTTLSeconds(default 60), so waiting works too.
"Checksum mismatch"
⚠ Warning Checksum mismatch: 2026...-add-users-index.jsWhy: the file was edited after it was already applied. migronaut detects this to stop you silently changing history.
Fix:
- Never edit an applied migration. Instead, write a new migration for the change.
- If the edit was intentional and harmless (a comment, formatting), the warning is informational in the default (non-strict) mode and the file is skipped.
- If you truly need to re-run it, that's
migronaut up <file> --force— but understand you're rewriting history. Seemigronaut up --force.
"Connection failed"
ConnectionFailedError: Failed to connect to MongoDBWhy: the uri/dbName is wrong, MongoDB isn't running, or the network/credentials are off.
Fix:
- Verify the server is up:
mongosh "<your-uri>". - Check your config or env vars (
MIGRONAUT_URI,MIGRONAUT_DB). See Configuration. - In Docker/CI, make sure the host is reachable (often
mongodb://mongo:27017, notlocalhost).
A .ts migration won't load
Cannot import a TypeScript migration: ERR_UNKNOWN_FILE_EXTENSIONWhy: you're on a Node version below 22.18, which can't import .ts directly, and no TypeScript loader is registered. migronaut runs under your Node and does not bundle a loader.
Fix: any one of:
- Upgrade to Node ≥ 22.18 —
.tsthen loads natively, no setup. - Register a loader: install
tsxand run migronaut under it, e.g.node --import tsx node_modules/@alexify/migronaut/bin/migronaut.js up. - Use
.jsinstead (migronaut create <name> --js) — runs with zero setup on any supported Node.
See Running TypeScript migrations for the full breakdown.
"MODULE_TYPELESS_PACKAGE_JSON" warning on every command
(node:48213) [MODULE_TYPELESS_PACKAGE_JSON] Warning: Module type of file:///app/migronaut.config.js
is not specified and it doesn't parse as CommonJS. Reparsing as ES module because
export syntax was detected.Why: Node's, not migronaut's. Your config or migration file uses ESM syntax (export default, export async function up), but the nearest package.json has no "type" field. Node has to parse the file twice to find out which module system it is, and warns each time. migronaut loads these files with dynamic import() — the one mechanism that handles .ts, .mjs, .cjs and .js uniformly — so the file reaches Node exactly as you wrote it.
It is only a warning: nothing is broken, and every command still works.
Fix: make the module type explicit, whichever suits the project:
- Declare it once — add
"type": "module"to yourpackage.jsonif the project is ESM. - Rename the file —
migronaut.config.mjs(or.mts) is unambiguously ESM,.cjsunambiguously CommonJS. Both load fine. - Use CommonJS syntax in a
"type"-less project:jsIn a// migronaut.config.js module.exports = { uri: 'mongodb://localhost:27017', dbName: 'my_app' };.tsconfig,import type { MigronautConfig } from '@alexify/migronaut'disappears during type-stripping, so it can be paired withmodule.exportsand stays warning-free.
migronaut init and migronaut create already generate whichever syntax matches the "type" of your nearest package.json, so files they create never trigger this.
"Transaction numbers are only allowed on a replica set"
Why: you set useTransaction but your MongoDB is a standalone server. Transactions require a replica set or sharded cluster.
Fix:
- Run a single-node replica set locally, or use a managed cluster (Atlas) which already is one.
- Or drop
useTransactionfor that migration if you don't need atomicity.
See Transactions.
"Migration is not applied"
NotAppliedError: 2026...-add-users-index.js has not been appliedWhy: you tried to down (revert) a migration that isn't currently applied.
Fix: run migronaut status to see what's actually applied, then target a file that is.
An imported migration won't revert
IrreversibleMigrationError: 2026...-legacy.js was imported from migrate-mongo and cannot be revertedWhy: migrations adopted via migronaut import use migrate-mongo's positional up(db, client) signature, which migronaut can't run safely in reverse. This is intentional — it's caught before anything is touched.
Fix: imported history is forward-only. To undo such a change, write a new migration that performs the reverse operation.
"Converge refused: … conflict(s)"
✖ CONVERGE_FAILED: Converge refused: 1 conflict(s) — users index "email_1": the undeclared index "by_email" covers the same key — …Why: a declared index has the same key (and partial filter and collation) as a live index with another name that you did not declare. MongoDB keeps only one of them, and converge never drops an undeclared index unless asked to.
Fix: declare the index under the name it already has (name: 'by_email'), or run migronaut converge --prune to replace it. Nothing was written.
"Atlas Search is not available on this server"
✖ CONVERGE_FAILED: Converge refused: 1 conflict(s) — movies search index "default": Atlas Search is not available on this server — use Atlas, …Why: a definition declares searchIndexes, and the server has no Atlas Search — a plain mongod, or an older Atlas CLI deployment. Nothing was written, in any collection.
Fix: converge against Atlas, an Atlas CLI local deployment (the mongodb/mongodb-atlas-local Docker image), or MongoDB 8.3+ with mongot. Where Search is expected to be missing — a plain mongo container in development — set onSearchUnavailable: 'skip' (or MIGRONAUT_ON_SEARCH_UNAVAILABLE=skip): everything else converges, and the search indexes are reported as skip.
A search index stays PENDING / BUILDING, or FAILED
Why: search indexes build in the background, after converge has returned — a large collection takes a while. A FAILED build carries the server's reason in the plan's Detail column and in result.search.notReady[].message (too many fields for the tier, an invalid mapping, …). converge does not resubmit a definition that has not changed, so a failed build stays failed.
Fix: for a slow build, wait — migronaut converge --wait-search blocks until it is queryable, and converge --dry-run shows where it is. For a failed one, change the definition (or the data behind the failure) and converge again; converge --check exits 28 until it builds.
--wait-search fails on a FAILED build only for an index the same run created or changed. One that failed before — its definition unchanged, so converge cannot fix it — is named in a line and a warning, and the wait goes on without it: a deploy is not held up by a build it did not start.
A search index is STALE
Why: the index still answers queries, but it has stopped replicating from the collection — its results may be out of date. It is not a build in progress, so waiting longer does not help: --wait-search keeps polling for an index the run created or changed until the budget runs out, and does not wait at all for one it did not touch. converge and migronaut audit warn about it.
Fix: see the Atlas Search documentation on index statuses for your cluster. Once the index is READY again, nothing is left for converge to do.
"Could not modify search index … "mappings" is required"
Why: an Atlas CLI local deployment (the mongodb/mongodb-atlas-local image) cannot update a vector search index in place — it refuses the update with or without the index type. Atlas can.
Fix: declare the changed vector index under a new name, converge, then remove the old declaration and converge with --prune. Against Atlas, the same definition updates in place.
"the type cannot change in place" / "autoEmbed … cannot change in place"
Why: no update can turn a search index into a vector one (or back), or give an automated- embedding field another path, model, size, quantization or modality — and dropping the index to build it again would leave every $search returning nothing until the build ends.
Fix: declare the new index under a new name, converge with --wait-search, move your queries to it, then remove the old declaration and converge with --prune.
"Could not create index … E11000 duplicate key error"
Why: a unique index cannot be built over documents that already share a value. When the index was being rebuilt, converge put the old one back — context.restored says whether it could.
Fix: deduplicate the data in a migration, then converge again (with convergeAfterUp, the next up does both, in that order).
"not authorized … collMod"
Why: changing a validator, a TTL or hidden uses collMod, which needs the dbAdmin role; plain readWrite covers creating and dropping indexes only.
Fix: grant dbAdmin on the database to the user converge runs as, or converge from a deploy step that has it.
Still stuck?
- Run any command with
--jsonto get a structured error object you can inspect. - Check the Error Codes reference for the exact
codeand its meaning. - Open an issue: https://github.com/Alexis-Technologies/migronaut/issues.