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
- Open a new connection with turso_serverless.connect(url, auth_token=...) — a closed Connection cannot be reopened
- Move close() to the outermost scope (process exit / app shutdown) so it runs strictly after all statements
- Give each thread or task its own Connection instead of sharing one and closing it from another path
- 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
- Give each thread/task its own Connection; never close a connection from a different path than the one that owns it
- Put close() in the single outermost try/finally; nothing may run after it
- Remember 'with conn:' commits/rollbacks but does NOT close — add an explicit close at top level
- In request-scoped apps, open the connection per request or use a pool that checks freshness before lending
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
- Cannot operate on a closed cursor
- Cannot operate on a closed connection
- Cannot operate on a closed cursor
- Cannot operate on a closed cursor
- Database is closed
AI-assisted analysis of tursodatabase/turso@bad083fafb (2026-08-16).
Data as JSON: /api/errors/4d5442d54233915d.
Report an issue: GitHub.