drizzle-team/drizzle-orm · warning · TransactionRollbackError
Rollback
Error message
Rollback
What it means
PgTransaction.rollback (session.ts:256) intentionally throws a TransactionRollbackError (a DrizzleError subclass named 'TransactionRollbackError' with message 'Rollback'). It is a control-flow signal: Drizzle's transaction wrapper catches it to issue ROLLBACK and abort the callback cleanly. It is not a bug — it is the supported way to abort a transaction from inside.
Source
Thrown at drizzle-orm/src/pg-core/session.ts:257
TSchema extends TablesRelationalConfig = Record<string, never>,
> extends PgDatabase<TQueryResult, TFullSchema, TSchema> {
static override readonly [entityKind]: string = 'PgTransaction';
constructor(
dialect: PgDialect,
session: PgSession<any, any, any>,
protected schema: {
fullSchema: Record<string, unknown>;
schema: TSchema;
tableNamesMap: Record<string, string>;
} | undefined,
protected readonly nestedIndex = 0,
) {
super(dialect, session, schema);
}
rollback(): never {
throw new TransactionRollbackError();
}
/** @internal */
getTransactionConfigSQL(config: PgTransactionConfig): SQL {
const chunks: string[] = [];
if (config.isolationLevel) {
chunks.push(`isolation level ${config.isolationLevel}`);
}
if (config.accessMode) {
chunks.push(config.accessMode);
}
if (typeof config.deferrable === 'boolean') {
chunks.push(config.deferrable ? 'deferrable' : 'not deferrable');
}
return sql.raw(chunks.join(' '));
}
setTransaction(config: PgTransactionConfig): Promise<void> {View on GitHub (pinned to b7862528fd)
Solutions
- Let TransactionRollbackError propagate out of the callback so Drizzle's wrapper handles ROLLBACK.
- At the db.transaction() call site, catch TransactionRollbackError to treat it as an expected abort rather than a crash.
- Do not wrap tx.rollback() in a try/catch inside the callback that swallows it.
Example fix
// before
try {
await db.transaction(async (tx) => {
if (!ok) await tx.rollback();
});
} catch (e) { /* treated as crash */ }
// after
try {
await db.transaction(async (tx) => {
if (!ok) tx.rollback();
});
} catch (e) {
if (e instanceof TransactionRollbackError) {/* expected abort */}
else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Centralize transaction abort handling at the call site.
async function runTx<T>(db: any, fn: (tx: any) => Promise<T>): Promise<T | null> {
try {
return await db.transaction(fn);
} catch (e) {
if (e instanceof TransactionRollbackError) return null; // expected abort
throw e;
}
} Type guard
import { TransactionRollbackError } from 'drizzle-orm/errors';
function isRollback(e: unknown): e is TransactionRollbackError {
return e instanceof TransactionRollbackError;
} Try / catch
import { TransactionRollbackError } from 'drizzle-orm/errors';
try {
await db.transaction(async (tx) => {
if (!shouldCommit) tx.rollback();
await tx.insert(t).values(row);
});
} catch (e) {
if (e instanceof TransactionRollbackError) {
// expected abort; nothing was committed
} else {
throw e; // real failure
}
} Prevention
- Catch TransactionRollbackError at the db.transaction() call site, not inside the callback.
- Do not swallow the error inside the transaction body.
- Use rollback() as a control-flow signal, not an exceptional failure.
When it happens
Trigger: Calling tx.rollback() inside a db.transaction(async (tx) => { ... }) callback when business logic decides the work should not commit. The thrown error propagates unless caught by the wrapper.
Common situations: Validation failure inside a transaction; optimistic concurrency conflict; partial work that must be discarded; wrapping rollback in a try/catch that accidentally swallows the signal.
Related errors
- Rollback
- Rollback
- Rollback
- Transactions are not supported by the MySql Proxy driver
- No transactions support in neon-http driver
AI-assisted analysis of drizzle-team/drizzle-orm@b7862528fd (2026-08-03).
Data as JSON: /data/errors/b4b262cb8daf0d08.json.
Report an issue: GitHub.