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
- Only abort on the error path and only when still active: if (session.inTransaction()) await session.abortTransaction().
- Do not put abortTransaction in a finally that also runs after a successful commit; keep abort strictly in catch.
- 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
- Keep abortTransaction strictly in the catch path, never in finally after commit.
- Gate abort with session.inTransaction() which is false after commit.
- Use withTransaction() to avoid manual abort entirely.
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
- No transaction started
- Cannot call commitTransaction after calling abortTransaction
- Cannot call abortTransaction twice
- 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/47b9cf2bc42873ec.json.
Report an issue: GitHub.