mongodb/node-mongodb-native · error · MongoTransactionError

Cannot call commitTransaction after calling abortTransaction

Error message

Cannot call commitTransaction after calling abortTransaction

What it means

Thrown by ClientSession.commitTransaction() when the transaction state is TRANSACTION_ABORTED. Once a transaction is aborted it cannot be committed; the spec forbids this transition. It is a MongoTransactionError.

Source

Thrown at src/sessions.ts:444

   *
   * @param options - Optional options, can be used to override `defaultTimeoutMS`.
   */
  async commitTransaction(options?: { timeoutMS?: number }): Promise<void> {
    if (this.transaction.state === TxnState.NO_TRANSACTION) {
      throw new MongoTransactionError('No transaction started');
    }

    if (
      this.transaction.state === TxnState.STARTING_TRANSACTION ||
      this.transaction.state === TxnState.TRANSACTION_COMMITTED_EMPTY
    ) {
      // the transaction was never started, we can safely exit here
      this.transaction.transition(TxnState.TRANSACTION_COMMITTED_EMPTY);
      return;
    }

    if (this.transaction.state === TxnState.TRANSACTION_ABORTED) {
      throw new MongoTransactionError(
        'Cannot call commitTransaction after calling abortTransaction'
      );
    }

    const command: {
      commitTransaction: 1;
      writeConcern?: WriteConcernSettings;
      recoveryToken?: Document;
      maxTimeMS?: number;
    } = { commitTransaction: 1 };

    const timeoutMS =
      typeof options?.timeoutMS === 'number'
        ? options.timeoutMS
        : typeof this.timeoutMS === 'number'
          ? this.timeoutMS
          : null;

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Track whether abort already ran and skip commit: only commit when state is STARTING_TRANSACTION/TRANSACTION_IN_PROGRESS.
  2. Use withTransaction() so the driver handles abort/commit transitions correctly.
  3. Reorder control flow so abort and commit are mutually exclusive branches (commit on success path, abort on error path).

Example fix

// before
try {
  session.startTransaction();
  await coll.insertOne(doc, { session });
} catch (e) {
  await session.abortTransaction();
  throw e;
} finally {
  await session.commitTransaction(); // throws after abort
}

// after
try {
  session.startTransaction();
  await coll.insertOne(doc, { session });
  await session.commitTransaction();
} catch (e) {
  if (session.inTransaction()) await session.abortTransaction();
  throw e;
}
Defensive patterns

Strategy: validation

Validate before calling

// only commit from the success path, when still active
if (session.inTransaction()) {
  await session.commitTransaction();
}

Type guard

function isCommittable(session: ClientSession): boolean {
  return session.transaction.isActive; // STARTING or IN_PROGRESS
}

Try / catch

try {
  await session.commitTransaction();
} catch (e) {
  if (e instanceof MongoTransactionError && /after calling abortTransaction/.test(e.message)) {
    // already aborted; swallow
  } else throw e;
}

Prevention

When it happens

Trigger: Calling commitTransaction() after abortTransaction() has already run on the same transaction; an error handler aborts and execution then falls through to a commit in the success/finally path.

Common situations: try { start; ops } catch { await abort } finally { await commit } - the finally re-attempts commit after the catch aborted; nested try blocks where the inner aborts but outer commits.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04). Data as JSON: /data/errors/5c6c77358667c37c.json. Report an issue: GitHub.