tursodatabase/turso · error · DatabaseError

Batch execution failed

Error message

Batch execution failed

What it means

Fatal, stream-level 'error' entry inside batch(). Per-statement failures arrive as step_error entries, which are captured as a deferred error so the trailing autocommit probe and (in atomic mode) the ROLLBACK step still drain; a top-level error entry means the whole cursor stream failed and is thrown immediately. The literal text is the fallback when the entry has no message.

Solutions

  1. Inspect the thrown message/code — the server usually names the offending statement
  2. Split the batch and run statements individually to isolate which one kills the stream
  3. Drop the mode argument (non-atomic) to see whether the implicit BEGIN/COMMIT wrapping is the trigger
  4. Reduce batch size or raise the query timeout if the failure correlates with long runtime

Example fix

// before
await session.batch(migrationStatements, 'write'); // one bad statement kills the stream

// after
for (const stmt of migrationStatements) {
  try { await session.execute(stmt); }
  catch (e) { console.error('failed:', stmt, e); throw e; }
}
Defensive patterns

Strategy: try-catch

Type guard

import { DatabaseError } from '@tursodatabase/serverless';

function isBatchError(e: unknown): e is DatabaseError {
  return e instanceof DatabaseError && e.message === 'Batch execution failed';
}

Try / catch

try {
  return await session.batch(statements, 'write');
} catch (e) {
  if (e instanceof DatabaseError) {
    // atomic mode already rolled back; per-statement message/code is on the error
    logFailedBatch(statements, e);
  }
  throw e;
}

Prevention

When it happens

Trigger: The server aborting the cursor stream mid-batch (connection/stream failure); batches whose statements are not allowed together (e.g. certain DDL inside the implicitly wrapped BEGIN...COMMIT of atomic mode); resource limits killing the stream. In atomic mode a user-step failure instead surfaces as the deferred DatabaseError after the rollback is observed.

Common situations: Large multi-migration batches run with a mode ('write'/'immediate') hitting a server limit; one statement in a long batch invalidating the stream; network interruption during a long batch.

Related errors


AI-assisted analysis of tursodatabase/turso@c1e5928725 (2026-08-31). Data as JSON: /api/errors/6c5c897b6b243f85. Report an issue: GitHub.

Appendix: source

Thrown at serverless/javascript/src/session.ts:619

      response = await executePipeline(this.httpContext(queryOptions), request, this.createAbortSignal(queryOptions));
    } catch (e) {
      this.baton = null;
      this.autocommit = true;
      throw e;
    }

    this.baton = response.baton;
    if (response.base_url) {
      this.baseUrl = normalizeUrl(response.base_url);
    }
    this.updateAutocommit(response);

    const first = response.results?.[0];
    if (!first) {
      throw new DatabaseError('missing batch result in pipeline response');
    }
    if (first.type === 'error') {
      throw new DatabaseError(first.error?.message || 'Batch execution failed', first.error?.code);
    }
    if (first.response?.type !== 'batch') {
      throw new DatabaseError(`expected batch result in pipeline response, got ${first.response?.type}`);
    }
    const batchResult = first.response.result as BatchResultData | undefined;
    const stepResults = batchResult?.step_results;
    const stepErrors = batchResult?.step_errors;
    if (
      !Array.isArray(stepResults) ||
      !Array.isArray(stepErrors) ||
      stepResults.length !== steps.length ||
      stepErrors.length !== steps.length
    ) {
      throw new DatabaseError('batch response does not have one result and one error per step');
    }

    // One result per user statement, in input order; null for statements
    // that did not complete.

View on GitHub (pinned to c1e5928725)