typeorm/typeorm · error · TransactionNotStartedError

Transaction is not started yet, start transaction before com

Error message

Transaction is not started yet, start transaction before committing or rolling it back.

What it means

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.

Source

Thrown at src/driver/aurora-postgres/AuroraPostgresQueryRunner.ts:149

                        this.isTransactionActive = false
                    }
                    throw err
                }
            }
        } else {
            await this.query(`SAVEPOINT typeorm_${this.transactionDepth}`)
        }
        this.transactionDepth += 1

        await this.broadcaster.broadcast("AfterTransactionStart")
    }

    /**
     * Commits transaction.
     * Error will be thrown if transaction was not started.
     */
    async commitTransaction(): Promise<void> {
        if (!this.isTransactionActive) throw new TransactionNotStartedError()

        await this.broadcaster.broadcast("BeforeTransactionCommit")

        if (this.transactionDepth > 1) {
            await this.query(
                `RELEASE SAVEPOINT typeorm_${this.transactionDepth - 1}`,
            )
        } else {
            await this.client.commitTransaction()
            this.isTransactionActive = false
        }
        this.transactionDepth -= 1

        await this.broadcaster.broadcast("AfterTransactionCommit")
    }

    /**
     * Rollbacks transaction.

View on GitHub (pinned to 04ff4daedc)

Solutions

  1. Always pair startTransaction() with commit/rollback and gate commit on isTransactionActive.
  2. Use the DataSource.transaction(async runner => ...) helper which manages the lifecycle for you.
  3. Reset/track transaction state in your wrapper so commit is only called once.

Example fix

// before
await queryRunner.commitTransaction() // no startTransaction first

// after
await queryRunner.startTransaction()
try { /* work */ await queryRunner.commitTransaction() }
catch { await queryRunner.rollbackTransaction() }
Defensive patterns

Strategy: validation

Validate before calling

if (queryRunner.isTransactionActive) await queryRunner.commitTransaction()

Type guard

const canCommit = (qr: QueryRunner): boolean => qr.isTransactionActive

Try / catch

try { await qr.commitTransaction() } catch (e) { if (e instanceof TransactionNotStartedError) { /* nothing to commit */ } else throw e }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of typeorm/typeorm@04ff4daedc (2026-08-03). Data as JSON: /data/errors/562c20268695d1c5.json. Report an issue: GitHub.