tursodatabase/turso · error · ProgrammingError

Cannot operate on a closed connection

Error message

Cannot operate on a closed connection

What it means

ProgrammingError raised by Connection._ensure_open() (connection.py:73-75) when any Connection method that calls it — cursor(), execute(), executemany(), executescript(), commit(), rollback() — runs after close(). It mirrors sqlite3 semantics: the closed connection object still exists but rejects every operation, and closing already rolled back any open transaction server-side. Note that the context-manager exit (__exit__) only commits or rolls back; it does not close, so hitting this error always involves an explicit close() followed by reuse.

Source

Thrown at serverless/python/turso_serverless/connection.py:75

    NotSupportedError = NotSupportedError
    Warning = Warning

    def __init__(
        self,
        session: Session,
        *,
        isolation_level: Optional[str] = "DEFERRED",
    ) -> None:
        self._session = session
        self.isolation_level = isolation_level
        self.row_factory: Callable | type[Row] | None = None
        self.text_factory: Any = str
        self._autocommit_mode: object | bool = "LEGACY"
        self._closed = False

    def _ensure_open(self) -> None:
        if self._closed:
            raise ProgrammingError("Cannot operate on a closed connection")

    def _execute_stmt(
        self,
        sql: str,
        params: Optional[list] = None,
        named_params: Optional[list[tuple[str, Any]]] = None,
        want_rows: bool = True,
    ) -> StmtResult:
        self._ensure_open()
        try:
            return self._session.execute_stmt(
                sql, args=params, named_args=named_params, want_rows=want_rows
            )
        except RuntimeError as e:
            raise _classify_error(e) from None

    @property
    def in_transaction(self) -> bool:

View on GitHub (pinned to bad083fafb)

Solutions

  1. Open a new connection with turso_serverless.connect(url, auth_token=...) — a closed Connection cannot be reopened
  2. Move close() to the outermost scope (process exit / app shutdown) so it runs strictly after all statements
  3. Give each thread or task its own Connection instead of sharing one and closing it from another path
  4. Remember 'with conn:' only commits/rollbacks — pair it with an explicit try/finally close at top level

Example fix

// before
conn = connect(URL, auth_token=TOKEN)
results = []
try:
    for q in queries:
        results.append(conn.execute(q).fetchall())
finally:
    conn.close()
extra = conn.execute("SELECT 1").fetchall()  # ProgrammingError: closed connection

// after
conn = connect(URL, auth_token=TOKEN)
try:
    for q in queries:
        results.append(conn.execute(q).fetchall())
    extra = conn.execute("SELECT 1").fetchall()
finally:
    conn.close()
Defensive patterns

Strategy: validation

Validate before calling

from turso_serverless.dbapi import ProgrammingError


def is_connection_open(conn) -> bool:
    """Check the driver's closed flag before touching the connection."""
    return not getattr(conn, "_closed", False)


# use before any deferred or pooled use
if not is_connection_open(conn):
    conn = connect(URL, auth_token=TOKEN)

Try / catch

from turso_serverless.dbapi import ProgrammingError

try:
    conn.execute("SELECT 1")
except ProgrammingError as e:
    if "closed connection" not in str(e):
        raise
    conn = connect(URL, auth_token=TOKEN)  # closed connections cannot reopen
    conn.execute("SELECT 1")

Prevention

When it happens

Trigger: Calling conn.execute(), conn.cursor(), conn.commit() or conn.rollback() after conn.close() returned. Typical shapes: a finally block (or atexit / middleware teardown) closing a shared connection while other code paths still hold it; closing inside a loop body but continuing the loop; a background thread using a connection the request handler closed.

Common situations: Web apps where middleware closes the per-app connection but request handlers still run; test fixtures closing connections while spawned tasks iterate cursors; refactors that moved close() earlier in the flow; code ported from drivers whose 'with conn:' closes the connection (here it does not, so developers add close() in the wrong scope).

Related errors


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