tursodatabase/turso · error · TypeError
batch statement ${index} failed: ${keyword} is not allowed i
Error message
batch statement ${index} failed: ${keyword} is not allowed in an atomic batch What it means
In atomic batch mode the library itself wraps statements in BEGIN/COMMIT, so any user statement starting with a transaction-control keyword (BEGIN, COMMIT, ROLLBACK, SAVEPOINT, etc.) is rejected up front with batchInputError(index, ...) naming the offending keyword. Nested or interleaved transaction control would break the atomicity guarantee the mode promises.
Source
Thrown at bindings/javascript/packages/common/promise.ts:808
}
try {
const args = Array.isArray(statement.args)
? statement.args.map(normalizeBatchBindValue)
: Object.fromEntries(
Object.entries(statement.args).map(([name, value]) => [name, normalizeBatchBindValue(value)]),
);
return { sql: statement.sql, args };
} catch (error) {
throw batchInputError(index, error);
}
});
if (wrap) {
for (let index = 0; index < normalizedStatements.length; index++) {
const statement = normalizedStatements[index];
const sql = typeof statement === "string" ? statement : statement.sql;
const keyword = firstSqlKeyword(sql);
if (keyword !== undefined && TRANSACTION_CONTROL_KEYWORDS.has(keyword)) {
throw batchInputError(index, new Error(`${keyword} is not allowed in an atomic batch`));
}
}
}
if (wrap) {
await runRawSql(`BEGIN ${normalizeBatchMode(mode!)}`);
}
const results: ResultSet[] = [];
const executeStatement = async (
statement: BatchStatement,
): Promise<ResultSet> => {
const sql = typeof statement === "string" ? statement : statement.sql;
const args = typeof statement === "string" ? undefined : statement.args;
let nativeStmt: NativeStatement;
try {
nativeStmt = native.prepare(sql);
} catch (err) {View on GitHub (pinned to c1e5928725)
Solutions
- Remove BEGIN/COMMIT/ROLLBACK/SAVEPOINT statements from the batch; the library handles the transaction when a mode is given.
- If you need manual transaction control, call batch() without a mode (no wrap) or use execute() for the transaction statements separately.
- Split the script so each batch contains only DML/DDL statements.
Example fix
// before await db.batch(["BEGIN", "INSERT INTO t VALUES (1)", "COMMIT"], "write"); // after await db.batch(["INSERT INTO t VALUES (1)"], "write"); // library wraps in BEGIN/COMMIT
Defensive patterns
Strategy: validation
Validate before calling
const TXN_KEYWORDS = new Set(["BEGIN","COMMIT","END","ROLLBACK","SAVEPOINT","RELEASE"]);
const assertNoTxnControl = (statements) => statements.forEach((s, i) => {
const sql = typeof s === "string" ? s : s.sql;
const kw = sql.trim().split(/\s+/)[0]?.toUpperCase();
if (TXN_KEYWORDS.has(kw)) throw new Error(`statement ${i}: ${kw} not allowed in atomic batch`);
}); Type guard
const isTransactionControl = (sql: string): boolean => TXN_KEYWORDS.has(sql.trim().split(/\s+/)[0]?.toUpperCase() ?? "");
Try / catch
try {
await db.batch(statements, "write");
} catch (e) {
if (/not allowed in an atomic batch/.test(e.message ?? "")) {
// strip txn statements and retry without a mode, or drop them
await db.batch(statements.filter(s => !isTransactionControl(typeof s === 'string' ? s : s.sql)), "write");
} else throw e;
} Prevention
- Never include BEGIN/COMMIT/ROLLBACK in batch statements — the mode adds them
- Use a mode-less batch() or execute() when you need manual transaction control
- Lint SQL-building code for hardcoded transaction keywords
- Filter transaction statements out of imported SQL scripts before batching
When it happens
Trigger: Calling batch(statements, 'write'|'read'|<BatchMode>) where a statement's SQL starts with BEGIN/COMMIT/END/ROLLBACK/SAVEPOINT/RELEASE and the library is not already inside a transaction (so it adds its own wrap).
Common situations: Copy-pasting a SQL script (with BEGIN...COMMIT) into an atomic batch; dynamically generated SQL that includes transaction statements; switching an existing execute() script to batch() without removing transaction statements.
Related errors
- batch statement ${index} failed: ${message}
- batch statement ${index} failed: ${keyword} is not allowed i
- Expected first argument to be an array of statements
- Batch execution failed
- batch statement ${index} failed: ${message}
AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-31).
Data as JSON: /api/errors/78d24b055924de89.
Report an issue: GitHub.