{"record":{"id":"43a7a647d06594ae","repo":"mongodb/node-mongodb-native","slug":"no-transaction-started","errorCode":null,"errorMessage":"No transaction started","messagePattern":"No transaction started","errorType":"exception","errorClass":"MongoTransactionError","httpStatus":null,"severity":"error","filePath":"src/sessions.ts","lineNumber":431,"sourceCode":"        this.clientOptions?.writeConcern,\n      readPreference:\n        options?.readPreference ??\n        this.defaultTransactionOptions.readPreference ??\n        this.clientOptions?.readPreference,\n      maxCommitTimeMS: options?.maxCommitTimeMS ?? this.defaultTransactionOptions.maxCommitTimeMS\n    });\n\n    this.transaction.transition(TxnState.STARTING_TRANSACTION);\n  }\n\n  /**\n   * Commits the currently active transaction in this session.\n   *\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: {","sourceCodeStart":413,"sourceCodeEnd":449,"githubUrl":"https://github.com/mongodb/node-mongodb-native/blob/dce7939f86fb283e167ad709955abedb7bf23124/src/sessions.ts#L413-L449","documentation":"`commitTransaction` throws MongoTransactionError when the session's transaction state is `NO_TRANSACTION` — there is nothing to commit because `startTransaction` was never called on this session (or it was already reset).","triggerScenarios":"`session.commitTransaction()` without a prior `startTransaction()`; reusing a session whose previous transaction already committed/aborted and was not restarted; misrouted session in a wrapper.","commonSituations":"Helper that commits in a finally block even when start was skipped due to an earlier error; refactoring that removed the start call but left the commit; conditional transaction logic where the condition was false.","solutions":["Only call commitTransaction after a successful startTransaction and at least one operation.","Track transaction state yourself or check `session.inTransaction()` before committing.","Prefer `withTransaction` which handles the lifecycle automatically.","Make commit/abort calls conditional on having started: wrap in `if (session.inTransaction())`."],"exampleFix":"// before\nawait session.commitTransaction(); // no startTransaction was ever called\n\n// after\nsession.startTransaction();\ntry {\n  await collection.insertOne(doc, { session });\n  await session.commitTransaction();\n} catch (err) {\n  await session.abortTransaction();\n  throw err;\n}","handlingStrategy":"validation","validationCode":"function safeCommit(session) {\n  if (!session.inTransaction() && session.transaction?.state !== 'STARTING_TRANSACTION') {\n    throw new Error('no transaction in progress to commit');\n  }\n  return session.commitTransaction();\n}","typeGuard":"function hasStartedTxn(session) {\n  return session.inTransaction();\n}","tryCatchPattern":null,"preventionTips":["Only call commitTransaction after startTransaction and at least one operation.","Use `withTransaction` to remove manual lifecycle bugs.","Guard commits with `if (session.inTransaction())`."],"tags":["sessions","transactions","validation","state"],"backgroundTag":null,"analyzedSha":"dce7939f86fb283e167ad709955abedb7bf23124","analyzedAt":"2026-08-11T04:54:53.215Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}