{"record":{"id":"5c6c77358667c37c","repo":"mongodb/node-mongodb-native","slug":"cannot-call-committransaction-after-calling-abortt","errorCode":null,"errorMessage":"Cannot call commitTransaction after calling abortTransaction","messagePattern":"Cannot call commitTransaction after calling abortTransaction","errorType":"exception","errorClass":"MongoTransactionError","httpStatus":null,"severity":"error","filePath":"src/sessions.ts","lineNumber":444,"sourceCode":"   *\n   * @param options - Optional options, can be used to override `defaultTimeoutMS`.\n   */\n  async commitTransaction(options?: { timeoutMS?: number }): Promise<void> {\n    if (this.transaction.state === TxnState.NO_TRANSACTION) {\n      throw new MongoTransactionError('No transaction started');\n    }\n\n    if (\n      this.transaction.state === TxnState.STARTING_TRANSACTION ||\n      this.transaction.state === TxnState.TRANSACTION_COMMITTED_EMPTY\n    ) {\n      // the transaction was never started, we can safely exit here\n      this.transaction.transition(TxnState.TRANSACTION_COMMITTED_EMPTY);\n      return;\n    }\n\n    if (this.transaction.state === TxnState.TRANSACTION_ABORTED) {\n      throw new MongoTransactionError(\n        'Cannot call commitTransaction after calling abortTransaction'\n      );\n    }\n\n    const command: {\n      commitTransaction: 1;\n      writeConcern?: WriteConcernSettings;\n      recoveryToken?: Document;\n      maxTimeMS?: number;\n    } = { commitTransaction: 1 };\n\n    const timeoutMS =\n      typeof options?.timeoutMS === 'number'\n        ? options.timeoutMS\n        : typeof this.timeoutMS === 'number'\n          ? this.timeoutMS\n          : null;\n","sourceCodeStart":426,"sourceCodeEnd":462,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/dce7939f86fb283e167ad709955abedb7bf23124/src/sessions.ts#L426-L462","documentation":"`commitTransaction` throws MongoTransactionError when the transaction state is `TRANSACTION_ABORTED` — once a transaction has been aborted it cannot be committed. The abort is terminal for that transaction attempt.","triggerScenarios":"`session.startTransaction(); ... await session.abortTransaction(); await session.commitTransaction();` — usually an error-handling block that aborts then a finally block that also tries to commit.","commonSituations":"Two try/catch layers each doing transaction cleanup; a generic cleanup that always commits in finally clashing with an inner catch that aborts; refactor that introduced a duplicate commit path.","solutions":["Use a single owner for the transaction lifecycle; let one place commit or abort.","Switch to `withTransaction` which never reaches this state.","After aborting, decide explicitly whether to start a new transaction rather than committing the aborted one."],"exampleFix":"// before\ntry {\n  session.startTransaction();\n  await collection.insertOne(doc, { session });\n} catch (err) {\n  await session.abortTransaction();\n} finally {\n  await session.commitTransaction(); // throws\n}\n\n// after\ntry {\n  session.startTransaction();\n  await collection.insertOne(doc, { session });\n  await session.commitTransaction();\n} catch (err) {\n  await session.abortTransaction();\n  throw err;\n}","handlingStrategy":"validation","validationCode":"// Decide abort-or-commit in exactly one place; never commit after abort.\nfunction cleanup(session, { shouldCommit }) {\n  if (shouldCommit && session.inTransaction()) return session.commitTransaction();\n  if (session.inTransaction()) return session.abortTransaction();\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Single-owner the transaction lifecycle.","Switch to `withTransaction`.","After aborting, start a new transaction if needed instead of committing the aborted one."],"tags":["sessions","transactions","validation","state","lifecycle"],"backgroundTag":null,"analyzedSha":"dce7939f86fb283e167ad709955abedb7bf23124","analyzedAt":"2026-08-11T04:54:53.215Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}