mongodb/node-mongodb-native · error · MongoTransactionError

Cannot call abortTransaction twice

Error message

Cannot call abortTransaction twice

What it means

`abortTransaction` throws MongoTransactionError when the state is already `TRANSACTION_ABORTED` — calling abort twice is rejected because the first abort already ended the transaction.

Solutions

  1. Own the abort in exactly one place; do not double-abort.
  2. Guard with `if (session.inTransaction()) await session.abortTransaction();` — once aborted, `inTransaction()` returns false.
  3. Use `withTransaction` to avoid manual abort altogether.

Example fix

// before
await session.abortTransaction();
await session.abortTransaction(); // throws

// after
if (session.inTransaction()) {
  await session.abortTransaction();
}
Defensive patterns

Strategy: validation

Validate before calling

function safeAbort(session) {
  if (session.inTransaction()) return session.abortTransaction();
}

Type guard

function canAbortTxn(session) {
  return session.inTransaction();
}

Prevention

When it happens

Trigger: `session.abortTransaction(); session.abortTransaction();` — typically nested error handlers where both the inner and outer catch attempt to abort.

Common situations: A user callback that aborts, then an outer wrapper that aborts again on the propagated error; multiple finally blocks; refactor that duplicated cleanup.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11). Data as JSON: /api/errors/9653dc156bfc3c7a. Report an issue: GitHub.

Appendix: source

Thrown at src/sessions.ts:587

   *
   * @param options - Optional options, can be used to override `defaultTimeoutMS`.
   */
  async abortTransaction(options?: { timeoutMS?: number }): Promise<void>;
  /** @internal */
  async abortTransaction(options?: { timeoutMS?: number; throwTimeout?: true }): Promise<void>;
  async abortTransaction(options?: { timeoutMS?: number; throwTimeout?: true }): Promise<void> {
    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 =

View on GitHub (pinned to dce7939f86)