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
- Track whether abort already ran and skip commit: only commit when state is STARTING_TRANSACTION/TRANSACTION_IN_PROGRESS.
- Use withTransaction() so the driver handles abort/commit transitions correctly.
- 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
- Never call commitTransaction from a finally that also runs after abort.
- Restrict commit to the success branch and abort to the error branch.
- Track an 'aborted' boolean in scope to short-circuit commit.
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
- No transaction started
- Cannot call abortTransaction twice
- 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/5c6c77358667c37c.json.
Report an issue: GitHub.