mongodb/node-mongodb-native · error · MongoTransactionError

Cannot call abortTransaction after calling commitTransaction

Error message

Cannot call abortTransaction after calling commitTransaction

What it means

Thrown by ClientSession.abortTransaction() when the transaction state is TRANSACTION_COMMITTED or TRANSACTION_COMMITTED_EMPTY. Once committed, a transaction cannot be rolled back; the spec forbids this transition. It is a MongoTransactionError.

Source

Thrown at src/sessions.ts:594

    if (this.transaction.state === TxnState.NO_TRANSACTION) {
      throw new MongoTransactionError('No transaction started');
    }

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

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

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

    const command: {
      abortTransaction: 1;
      writeConcern?: WriteConcernOptions;
      recoveryToken?: Document;
    } = { abortTransaction: 1 };

    const timeoutMS =
      typeof options?.timeoutMS === 'number'
        ? options.timeoutMS
        : this.timeoutContext?.csotEnabled()
          ? this.timeoutContext.timeoutMS // refresh timeoutMS for abort operation
          : typeof this.timeoutMS === 'number'
            ? this.timeoutMS
            : null;

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Only abort on the error path and only when still active: if (session.inTransaction()) await session.abortTransaction().
  2. Do not put abortTransaction in a finally that also runs after a successful commit; keep abort strictly in catch.
  3. Use withTransaction() so commit/abort sequencing is handled by the driver.

Example fix

// before
try {
  session.startTransaction();
  await coll.insertOne(doc, { session });
  await session.commitTransaction();
} finally {
  await session.abortTransaction(); // throws: already committed
}

// 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

// abort only on error, only if still active
if (session.inTransaction()) {
  await session.abortTransaction();
}

Type guard

function isAbortable(session: ClientSession): boolean {
  return session.transaction.isActive;
}

Try / catch

try {
  await session.abortTransaction();
} catch (e) {
  if (e instanceof MongoTransactionError && /after calling commitTransaction/.test(e.message)) {
    // committed; cannot abort, ignore
  } else throw e;
}

Prevention

When it happens

Trigger: A finally block calls abortTransaction() after commitTransaction() has already succeeded; re-aborting after a commit attempt.

Common situations: try { ...; await commit; } finally { await abort; } - the finally aborts a committed transaction; success path commits, then cleanup aborts.

Related errors


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