tursodatabase/turso · error · LibsqlError

CLIENT_CLOSED

CLIENT_CLOSED

Error message

Client is closed

What it means

execute() checks the _closed flag under the client's exec lock and throws LibsqlError code CLIENT_CLOSED if the client was closed earlier. The compatibility client is not reconnectable: after close() every execute (and batch/transaction) on the same instance fails with this error.

Solutions

  1. Recreate the client: call createClient(config) again to get a fresh, open instance
  2. Check the client.closed getter before issuing queries on shared instances
  3. Scope close() to true app shutdown only; in serverless runtimes keep the client alive across invocations instead of closing per request
  4. Audit finally blocks and error handlers for premature close() calls

Example fix

// before
finally { client.close(); }
// next request:
await client.execute('SELECT 1'); // throws CLIENT_CLOSED

// after - lazy reusable client, never closed per request
let client = createClient(config);
export async function query(sql: string) {
  if (client.closed) client = createClient(config);
  return client.execute(sql);
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (client.closed) {
  client = createClient(config);
}
const rs = await client.execute(sql);

Type guard

function isOpen(client: Client): boolean {
  return !client.closed;
}

Try / catch

catch (e) { if (e instanceof LibsqlError && e.code === 'CLIENT_CLOSED') { client = createClient(config); return client.execute(sql); } throw e; }

Prevention

When it happens

Trigger: client.execute(sql) after client.close() ran - typically a shared module-level client closed by an error handler, a shutdown hook, or hot-reload cleanup, while later requests still reference it.

Common situations: Serverless handler closes the client in a finally block but the module-level singleton is reused by the next invocation; test teardown closing the client then assertions still running queries; close-on-error followed by a retry that reuses the same instance.

Related errors


AI-assisted analysis of tursodatabase/turso@6c72522679 (2026-08-20). Data as JSON: /api/errors/58d4619fd1845ce1. Report an issue: GitHub.

Appendix: source

Thrown at serverless/javascript/src/compat.ts:307

    if (options != null && typeof options === "object") {
      return {
        mode: options.mode,
        raw: options.raw === true,
      };
    }
    return {
      mode: options,
      raw: false,
    };
  }

  async execute(stmt: InStatement): Promise<ResultSet>;
  async execute(sql: string, args?: InArgs): Promise<ResultSet>;
  async execute(stmtOrSql: InStatement | string, args?: InArgs): Promise<ResultSet> {
    await this.execLock.acquire();
    try {
      if (this._closed) {
        throw new LibsqlError("Client is closed", "CLIENT_CLOSED");
      }

      let normalizedStmt: { sql: string; args: any[] };

      if (typeof stmtOrSql === 'string') {
        const normalizedArgs = args ? (Array.isArray(args) ? args : Object.values(args)) : [];
        normalizedStmt = { sql: stmtOrSql, args: normalizedArgs };
      } else {
        normalizedStmt = this.normalizeStatement(stmtOrSql);
      }

      const result = await this.session.execute(normalizedStmt.sql, normalizedStmt.args, this._defaultSafeIntegers);
      return this.convertResult(result);
    } catch (error: any) {
      if (error instanceof LibsqlError) {
        throw error;
      }
      throw mapDatabaseError(error, "EXECUTE_ERROR");

View on GitHub (pinned to 6c72522679)