{"id":"562c20268695d1c5","repo":"typeorm/typeorm","slug":"transaction-is-not-started-yet-start-transaction-562c20","errorCode":null,"errorMessage":"Transaction is not started yet, start transaction before committing or rolling it back.","messagePattern":"Transaction is not started yet, start transaction before committing or rolling it back\\.","errorType":"exception","errorClass":"TransactionNotStartedError","httpStatus":null,"severity":"error","filePath":"src/driver/aurora-postgres/AuroraPostgresQueryRunner.ts","lineNumber":149,"sourceCode":"                        this.isTransactionActive = false\n                    }\n                    throw err\n                }\n            }\n        } else {\n            await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)\n        }\n        this.transactionDepth += 1\n\n        await this.broadcaster.broadcast(\"AfterTransactionStart\")\n    }\n\n    /**\n     * Commits transaction.\n     * Error will be thrown if transaction was not started.\n     */\n    async commitTransaction(): Promise<void> {\n        if (!this.isTransactionActive) throw new TransactionNotStartedError()\n\n        await this.broadcaster.broadcast(\"BeforeTransactionCommit\")\n\n        if (this.transactionDepth > 1) {\n            await this.query(\n                `RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,\n            )\n        } else {\n            await this.client.commitTransaction()\n            this.isTransactionActive = false\n        }\n        this.transactionDepth -= 1\n\n        await this.broadcaster.broadcast(\"AfterTransactionCommit\")\n    }\n\n    /**\n     * Rollbacks transaction.","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/typeorm/typeorm/blob/04ff4daedcf60fa4ffd0d5d33bbafaac1a9bbc96/src/driver/aurora-postgres/AuroraPostgresQueryRunner.ts#L131-L167","documentation":"Thrown as TransactionNotStartedError by AuroraPostgresQueryRunner.commitTransaction when this.isTransactionActive is false — i.e. commitTransaction() was called before startTransaction() completed, or after the transaction already committed/rolled back. The guard prevents committing a non-existent transaction against the underlying client.","triggerScenarios":"Calling queryRunner.commitTransaction() without a preceding startTransaction(); committing twice (second call after the first set isTransactionActive=false); committing inside a code path where startTransaction was skipped due to an early return.","commonSituations":"Helper functions that wrap commit but are called from both inside and outside an existing transaction; error-handling code that commits in a finally block even when startTransaction threw; misordered await chains where startTransaction() is not awaited.","solutions":["Always pair startTransaction() with commit/rollback and gate commit on isTransactionActive.","Use the DataSource.transaction(async runner => ...) helper which manages the lifecycle for you.","Reset/track transaction state in your wrapper so commit is only called once."],"exampleFix":"// before\nawait queryRunner.commitTransaction() // no startTransaction first\n\n// after\nawait queryRunner.startTransaction()\ntry { /* work */ await queryRunner.commitTransaction() }\ncatch { await queryRunner.rollbackTransaction() }","handlingStrategy":"validation","validationCode":"if (queryRunner.isTransactionActive) await queryRunner.commitTransaction()","typeGuard":"const canCommit = (qr: QueryRunner): boolean => qr.isTransactionActive","tryCatchPattern":"try { await qr.commitTransaction() } catch (e) { if (e instanceof TransactionNotStartedError) { /* nothing to commit */ } else throw e }","preventionTips":["Prefer DataSource.transaction(...) over manual start/commit.","Always gate commit/rollback on isTransactionActive.","Track started-state in wrappers to avoid double commit."],"tags":["aurora-postgres","transaction","typeorm"],"analyzedSha":"04ff4daedcf60fa4ffd0d5d33bbafaac1a9bbc96","analyzedAt":"2026-08-03T18:27:32.281Z","schemaVersion":2}