Skip to content

Error Codes ​

Every error thrown by migronaut extends MigronautError and carries a typed code, a message, and an optional context object. Catch MigronautError and switch on code for precise handling:

ts
import { MigronautError } from '@alexify/migronaut';

try {
  await migrator.up();
} catch (err) {
  if (err instanceof MigronautError) {
    console.error(err.code, '—', err.message, err.context);
  }
}

Every error also carries cause — the original Error, with its stack — when it wraps one, so err.cause.stack still points into your own migration. Pass --verbose to have the CLI print it.

In --json mode the CLI prints:

json
{
  "error": { "code": "MIGRATION_EXECUTION_FAILED", "message": "…", "context": { "…": "…" } },
  "partial": [{ "file": "0001-a.ts", "status": "applied", "duration": 12, "batch": 3 }]
}

partial lists what already succeeded before the failure, so a deploy pipeline can tell how far the run got. A failed converge carries its own progress in error.context.converge instead — partial stays the migrations, so after an up that converges it still lists the ones that were applied. The exit code identifies the failure — see Exit codes.

Reference ​

CodeError classWhen it's thrownWhat to do
LOCK_ALREADY_HELDLockAlreadyHeldErrorAnother run holds the lock within its TTLWait, or migronaut unlock if it's stale
LOCK_RELEASE_FAILEDLockReleaseFailedErrorThe lock couldn't be releasedCheck DB connectivity; retry
LOCK_LOSTLockLostErrorThe lock was lost mid-run (reclaimed, or the heartbeat couldn't reach the DB)Check what else is migrating; re-run up once it's clear
RUN_ABORTEDRunAbortedErrorThe run was stopped by stop() or SIGINT/SIGTERMSee context.results for what was applied, then re-run
HOOK_FAILEDHookFailedErrorOne of your lifecycle hooks threwcontext.hook names it; context.cause has the message
CHECKSUM_MISMATCHChecksumMismatchErrorAn applied file was edited (in --strict)Don't edit applied files — write a new migration
MIGRATION_FILE_NOT_FOUNDMigrationFileNotFoundErrorA named migration file doesn't existCheck the filename and migrationsDir
MIGRATION_FILE_EXISTSMigrationFileExistsErrormigronaut create would overwrite an existing filePick a different name, or delete the existing file
MIGRATION_INVALID_NAMEMigrationInvalidNameErrorA migration name escapes the migrations dir, or isn't a stringUse a bare filename, not a path
MIGRATION_INVALID_EXPORTMigrationInvalidExportErrorA file is missing up/down functionsExport both up and down
MIGRATION_EXECUTION_FAILEDMigrationExecutionFailedErrorA migration's up/down threwRead the cause; fix the migration logic
MIGRATION_TIMEOUTMigrationTimeoutErrorA migration ran longer than timeoutMsRaise the limit, or make the migration watch ctx.signal
TRANSACTIONS_UNSUPPORTEDTransactionsUnsupportedErroruseTransaction is on, but the deployment is standaloneSet useTransaction: false, or run a replica set / mongos
CONFIG_INVALIDConfigInvalidErrorConfig failed validationCheck required fields and types
CONFIG_FILE_EXISTSConfigFileExistsErrormigronaut init found an existing configUse --force to overwrite
CONNECTION_FAILEDConnectionFailedErrorCouldn't connect to MongoDBVerify uri/dbName and that Mongo is up
NOT_APPLIEDNotAppliedErrorTried to revert a migration that isn't appliedRun migronaut status to see what's applied
IMPORT_TARGET_NOT_EMPTYImportTargetNotEmptyErrormigronaut import target already has recordsUse --force to import anyway
MIGRATION_IRREVERSIBLEIrreversibleMigrationErrorTried to revert a forward-only record (imported or baselined)Write a new forward migration instead
MIGRATION_OUT_OF_ORDEROutOfOrderMigrationErrorA bulk up under onOutOfOrder: 'error' found a pending migration sorting before the newest applied one (a file merged late from a parallel branch)Apply it deliberately with onOutOfOrder: 'warn' or 'allow'
MIGRATION_BLOCKEDMigrationBlockedErrorAn ordered single-file run (every queue job is one) would go out of sequence: an earlier migration is still pending (up), or one applied later is still applied (down)context.blockedBy names what must go first — fix or apply those, then enqueue again
QUEUE_JOB_INVALIDQueueJobInvalidErrorA queue job's payload failed the contract check — unknown job name or data version, a migration name that is not a bare filenameEnqueue through enqueueUp/enqueueDown; context.issue says what was wrong
QUEUE_JOB_FAILEDQueueJobFailedErrorA queue group's wait() saw one of its jobs fail, or timed outcontext.failedReason is the worker's message, context.results what finished before it
CONVERGE_FAILEDConvergeFailedErrorconverge could not bring the declared collections to their declared state: the plan had a conflict, or a search index list could not be read before the first write (context.phase: 'plan' — nothing was written); a collection changed while the run was under way ('replan' — that collection was not touched); a step failed ('apply'); or waitForSearchIndexes gave up — on a FAILED build of an index the run created or changed, its timeout, or a search index list it could not read ('wait', context.reason 'failed', 'timeout' or 'unreadable' — everything was applied, context.notReady lists the builds). A search index list that cannot be read later in the run is reported in the phase that read it ('replan', 'apply', 'wait')context.hint says what usually fixes the server error (deduplicate before a unique index, grant dbAdmin for validators, use a server with Atlas Search or onSearchUnavailable: 'skip'); context.converge is the result so far, context.restored whether a failed rebuild put the old index back
REVISION_CONFLICTRevisionConflictErrorA revision-guarded write from @alexify/migronaut/versioning matched nothing: the document is at another revision (context.reason: 'conflict', context.actual), it does not exist ('not-found'), or the follow-up read could not tell ('unknown')Re-read and retry (retryOnConflict), or answer the caller with a conflict — HTTP 409 is the usual mapping
SHAPE_VERSION_UNSUPPORTEDShapeVersionErrorAn upcaster could not bring a document to the current shape: it was written by a newer release (context.reason: 'newer'), no step brings it up — it is older than the oldest shape the upcaster has steps for ('below-min'; versioning.min unless steps for older versions are given), or its version field is not a non-negative integer ('invalid')'newer' during a rolling deploy means an old pod read a new document — finish the deploy; 'below-min' means a background migration has not reached it
BACKGROUND_PENDINGBackgroundPendingErrorA migration requires a background migration that has not completed, or whose collection still holds old-shape documentsNothing ran; context.waitsFor lists what it waits for — let the background migration finish, then run up again (or use onBackgroundPending: 'stop')
BACKGROUND_FAILEDBackgroundFailedErrorA background migration ended failed — a partition ran out of slice failures, the document error budget was spent, or old-shape documents kept appearing for maxPasses passescontext.lastError says what happened; fix it, then migronaut background retry
BACKGROUND_CONFLICTBackgroundConflictErrorA background control action did not fit its state (pause a completed one, resume one that is not paused, retry one still running)context.status is the state found, context.action what was asked — check migronaut background status
SANDBOX_REFUSEDSandboxRefusedErrorA background dry run reached for something the always-aborted sandbox cannot run — DDL, an admin command, another session, $out/$merge, a migronaut collectioncontext.method names the call; the real run is unaffected — dry-run the rest with --sample or move the DDL to a regular migration

See Troubleshooting for step-by-step fixes for the most common ones.

Released under the MIT License.