mongodb/node-mongodb-native · error · MongoTransactionError
Cannot call abortTransaction twice
Error message
Cannot call abortTransaction twice
What it means
Thrown by ClientSession.abortTransaction() when the transaction state is already TRANSACTION_ABORTED. Double-abort is rejected by the driver because the first abort already transitioned the state machine out of an abortable state. It is a MongoTransactionError.
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 3366c21a63)
Solutions
- Guard with active-state check: if (session.inTransaction()) await session.abortTransaction(). Note inTransaction() returns false once aborted, so this naturally prevents double-abort.
- Centralize abort in one place (either catch or finally, not both).
- Switch to withTransaction() to avoid manual abort management.
Example fix
// before
try { session.startTransaction(); await coll.insertOne(doc, { session }); }
catch (e) { await session.abortTransaction(); throw e; }
finally { await session.abortTransaction(); } // throws: already aborted
// after
try { session.startTransaction(); await coll.insertOne(doc, { session }); }
catch (e) { throw e; }
finally { if (session.inTransaction()) await session.abortTransaction(); } Defensive patterns
Strategy: validation
Validate before calling
if (session.inTransaction()) {
await session.abortTransaction(); // inTransaction() is false once aborted
} Type guard
function isAbortable(session: ClientSession): boolean {
return session.transaction.isActive;
} Try / catch
try {
await session.abortTransaction();
} catch (e) {
if (e instanceof MongoTransactionError && /abortTransaction twice/.test(e.message)) {
// already aborted; safe to ignore
} else throw e;
} Prevention
- Centralize abort in one location (catch OR finally, not both).
- Always gate abort on session.inTransaction().
- Use withTransaction() so abort is attempted at most once per attempt.
When it happens
Trigger: A catch block calls abortTransaction() and then a finally block calls abortTransaction() again; explicit abort followed by another explicit abort.
Common situations: Boilerplate try/catch/finally where both catch and finally abort; nested functions where each layer defensively aborts.
Related errors
- No transaction started
- Cannot call commitTransaction after calling abortTransaction
- Cannot call abortTransaction after calling commitTransaction
- Transaction already in progress
- Transactions are not supported in snapshot sessions
AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04).
Data as JSON: /data/errors/9653dc156bfc3c7a.json.
Report an issue: GitHub.