tursodatabase/turso · error · Error

Statement finalization failed with status: ${status}

Error message

Statement finalization failed with status: ${status}

What it means

Statement.finalize() loops the native finalize call, servicing TursoStatus.IO by running runIo() and draining _extraIo; a non-DONE status throws with its numeric code (decode via TursoStatus: 4=BUSY, 127=ERROR, 128=MISUSE). Because this._finalized = true is only assigned after the loop breaks successfully, a throw here leaves the statement NOT marked finalized — finalize can be retried.

Source

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

    }
    try {
      while (true) {
        const status = this._statement.finalize();

        if (status === TursoStatus.IO) {
          // Statement needs IO (e.g., loading missing pages with partial sync)
          this._statement.runIo();

          // Drain sync engine IO queue
          if (this._extraIo) {
            await this._extraIo();
          }

          continue;
        }

        if (status !== TursoStatus.DONE) {
          throw new Error(`Statement finalization failed with status: ${status}`);
        }
        break;
      }
      this._finalized = true;
    } finally {
      if (this._execLock) {
        this._execLock.release();
      }
    }
  }

  /**
   * Check if statement has been finalized
   */
  get finalized(): boolean {
    return this._finalized;
  }
}

View on GitHub (pinned to bad083fafb)

Solutions

  1. Decode the numeric status via the TursoStatus enum to identify the cause
  2. Await outstanding statements/operations before closing, so finalize runs uncontended
  3. Status 4 (BUSY): wait briefly and retry finalize() — it is safe to retry since _finalized was not set
  4. Status 127/IO-related: verify connectivity during teardown or accept an unclean close and let recovery handle it

Example fix

// before
await stmt.finalize(); // throws during teardown: Statement finalization failed with status: 4

// after
async function finalizeRetry(stmt, tries = 3) {
  for (let i = 0; ; i++) {
    try { return await stmt.finalize(); }
    catch (e) {
      if (i < tries - 1 && /status: 4$/.test(e.message)) {
        await new Promise(r => setTimeout(r, 50));
        continue;
      }
      throw e;
    }
  }
}
Defensive patterns

Strategy: retry

Type guard

function isFinalizeStatusError(e: unknown): boolean {
  return e instanceof Error && /Statement finalization failed with status: \d+/.test(e.message);
}

Try / catch

async function finalizeSafely(stmt: Statement, tries = 3): Promise<void> {
  for (let i = 0; ; i++) {
    try { return await stmt.finalize(); }
    catch (e) {
      const busy = e instanceof Error && e.message.endsWith(`status: ${TursoStatus.BUSY}`);
      if (i < tries - 1 && busy) {
        await new Promise(r => setTimeout(r, 50 * (i + 1)));
        continue; // safe: _finalized was not set on throw
      }
      throw e;
    }
  }
}

Prevention

When it happens

Trigger: Finalizing while the statement/database is BUSY (4) from another connection; pending sync IO that cannot complete during teardown (network gone while finalize needs to flush); native ERROR (127) during resource release.

Common situations: Closing a Database on app background while background sync still writes; finalizing inside an offline state where the IO drain cannot reach the sync server; teardown races at app exit.

Related errors


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