tursodatabase/turso · error · Error

Statement step failed with status: ${status}

Error message

Statement step failed with status: ${status}

What it means

Statement.get() steps the statement once via stepWithIo(); TursoStatus.ROW (2) yields the row and DONE (1) yields undefined. Any other status falls through to this throw with the numeric code, which decodes against the TursoStatus enum: 4=BUSY, 5=INTERRUPT, 127=ERROR, 128=MISUSE, 133=CORRUPT, 134=IOERR. Unlike rawRun's executor path, this is a single-step read path, so failure statuses here usually mean the step itself could not be performed.

Source

Thrown at bindings/react-native/src/Statement.ts:264

    try {
      // Bind parameters inside the lock to prevent concurrent bind/execute races
      if (params.length > 0) {
        this.bind(...params);
      }

      // Step once with async IO handling
      const status = await this.stepWithIo();

      if (status === TursoStatus.ROW) {
        const row = this.readRow();
        return row;
      }

      if (status === TursoStatus.DONE) {
        return undefined;
      }

      throw new Error(`Statement step failed with status: ${status}`);
    } finally {
      this._statement.reset();
      if (this._execLock) {
        this._execLock.release();
      }
    }
  }

  /**
   * Execute statement and return all rows
   *
   * @param params - Optional parameters to bind
   * @returns Array of rows
   */
  async all(...params: BindParams[]): Promise<Row[]> {
    if (this._finalized) {
      throw new Error('Statement has been finalized');
    }

View on GitHub (pinned to bad083fafb)

Solutions

  1. Decode the numeric status against the TursoStatus enum to identify the class of failure
  2. Status 4 (BUSY): retry get() after a short backoff, or schedule reads outside write windows
  3. Status 128 (MISUSE): ensure the statement was reset/rebound correctly between executions
  4. Status 127/133/134: verify database file integrity and that the sync engine finished applying changes

Example fix

// before
try {
  const row = await stmt.get(id);
} catch (e) { /* opaque failure */ }

// after
import { TursoStatus } from '@tursodatabase/sync-react-native';
try {
  const row = await stmt.get(id);
} catch (e) {
  const m = /status: (\d+)$/.exec(String(e.message));
  if (m && Number(m[1]) === TursoStatus.BUSY) {
    await new Promise(r => setTimeout(r, 50));
    return await stmt.get(id); // one retry
  }
  throw e;
}
Defensive patterns

Strategy: try-catch

Type guard

function isTursoStatusError(e: unknown): boolean {
  return e instanceof Error && /status: \d+$/.test(e.message);
}

Try / catch

try {
  const row = await stmt.get(id);
} catch (e) {
  const m = /status: (\d+)$/.exec(String((e as Error).message));
  const code = m ? Number(m[1]) : null;
  if (code === TursoStatus.BUSY) { /* backoff and retry once */ }
  else if (code === TursoStatus.MISUSE) { /* reset + rebind, then retry */ }
  else throw e;
}

Prevention

When it happens

Trigger: A SELECT via stmt.get() hitting a BUSY (4) database when another connection holds the write lock; a step interrupted mid-flight (5); MISUSE (128) from stepping a statement in a bad state after abnormal bind/reset sequences; engine-level ERROR (127) from a corrupt or unreadable page.

Common situations: Concurrent read-during-write contention in multi-connection setups; background sync writing while the UI thread calls get(); a database file corrupted after an unclean shutdown.

Related errors


AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16). Data as JSON: /api/errors/72be116201fd7faf. Report an issue: GitHub.