drizzle-team/drizzle-orm · warning · TransactionRollbackError
Rollback
Error message
Rollback
What it means
Thrown by GelTransaction.rollback() (line 231) as a TransactionRollbackError. This is the intentional control-flow mechanism Drizzle uses to abort a transaction: calling tx.rollback() throws, which propagates out of the transaction callback and signals the session wrapper to issue a ROLLBACK instead of COMMIT.
Source
Thrown at drizzle-orm/src/gel-core/session.ts:231
TFullSchema extends Record<string, unknown> = Record<string, never>,
TSchema extends TablesRelationalConfig = Record<string, never>,
> extends GelDatabase<TQueryResult, TFullSchema, TSchema> {
static override readonly [entityKind]: string = 'GelTransaction';
constructor(
dialect: GelDialect,
session: GelSession<any, any, any>,
protected schema: {
fullSchema: Record<string, unknown>;
schema: TSchema;
tableNamesMap: Record<string, string>;
} | undefined,
) {
super(dialect, session, schema);
}
rollback(): never {
throw new TransactionRollbackError();
}
abstract override transaction<T>(
transaction: (tx: GelTransaction<TQueryResult, TFullSchema, TSchema>) => Promise<T>,
): Promise<T>;
}
export interface GelQueryResultHKT {
readonly $brand: 'GelQueryResultHKT';
readonly row: unknown;
readonly type: unknown;
}
export type GelQueryResultKind<TKind extends GelQueryResultHKT, TRow> = (TKind & {
readonly row: TRow;
})['type'];
View on GitHub (pinned to b7862528fd)
Solutions
- Recognize this is expected behavior: do NOT catch and ignore TransactionRollbackError unless you intend to suppress the rollback signal.
- If you catch errors around db.transaction, re-throw TransactionRollbackError so the session still rolls back.
- Use the return value of db.transaction for success and let rollback() handle abort - don't structure code around the throw.
- If you see this leak to a caller unexpectedly, ensure the transaction callback isn't wrapped in a try/catch that swallows it.
Example fix
// before - swallowing rollback breaks the signal
try {
await db.transaction(async (tx) => {
if (!ok) await tx.rollback();
});
} catch (e) { /* swallowed */ }
// after - let it propagate or re-throw
import { TransactionRollbackError } from 'drizzle-orm/errors';
try {
await db.transaction(async (tx) => {
if (!ok) tx.rollback();
});
} catch (e) {
if (e instanceof TransactionRollbackError) return; // expected
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Decide rollback before entering the transaction
if (!shouldCommit) return null;
return await db.transaction(async (tx) => { ... }); Type guard
import { TransactionRollbackError } from '~/errors.ts';
function isRollback(e: unknown): e is TransactionRollbackError {
return e instanceof TransactionRollbackError;
} Try / catch
try {
await db.transaction(async (tx) => {
if (!ok) tx.rollback();
// ...
});
} catch (e) {
if (e instanceof TransactionRollbackError) return; // expected
throw e;
} Prevention
- Never swallow TransactionRollbackError without re-throwing.
- Treat rollback() as control flow, not as an exceptional bug.
- Validate business rules before entering the transaction when possible.
When it happens
Trigger: User explicitly calls tx.rollback() inside a db.transaction(async (tx) => {...}) callback. Any code path that reaches rollback() will throw; the Gel session catches it and rolls back the underlying transaction.
Common situations: Business-rule validation inside a transaction decides the operation should not commit; a nested transaction (savepoint) needs to be aborted; wrapping rollback in custom error handling that accidentally swallows it.
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/ba1948e26d791895.json.
Report an issue: GitHub.