tursodatabase/turso · error · ProgrammingError

query timeout must be non-negative

Error message

query timeout must be non-negative

What it means

Connection.set_query_timeout validates that milliseconds is >= 0 before calling into the engine; 0 disables the timeout. A negative value raises ProgrammingError immediately, since a deadline in the past is meaningless.

Source

Thrown at bindings/python/turso/lib.py:454

        ``OperationalError`` ("interrupted"). The underlying execution methods
        release the GIL, so a watchdog thread can actually run while a query is
        busy. If no statement is running the call is a no-op.
        """
        try:
            self._conn.interrupt()
        except Exception as exc:  # noqa: BLE001
            raise _map_turso_exception(exc)

    def set_query_timeout(self, milliseconds: int) -> None:
        """
        Set the maximum time (in milliseconds) a single statement may run before
        it is interrupted (raising ``OperationalError``). ``0`` disables the
        timeout. Unlike ``interrupt()`` this needs no watchdog thread: the
        deadline is enforced inside the engine. Turso extension (not in stdlib
        ``sqlite3``).
        """
        if milliseconds < 0:
            raise ProgrammingError("query timeout must be non-negative")
        try:
            self._conn.set_query_timeout(milliseconds)
        except Exception as exc:  # noqa: BLE001
            raise _map_turso_exception(exc)

    def get_query_timeout(self) -> int:
        """Return the current per-statement query timeout in milliseconds (``0`` = disabled)."""
        try:
            return self._conn.get_query_timeout()
        except Exception as exc:  # noqa: BLE001
            raise _map_turso_exception(exc)

    def _maybe_implicit_begin(self, sql: str) -> None:
        """
        Implement sqlite3 legacy implicit transaction behavior:

        If autocommit is LEGACY_TRANSACTION_CONTROL, isolation_level is not None, sql is a DML
        (INSERT/UPDATE/DELETE/REPLACE), and there is no open transaction, issue:

View on GitHub (pinned to bad083fafb)

Solutions

  1. Clamp computed values: conn.set_query_timeout(max(0, ms))
  2. Use 0 to disable the timeout, not a negative number
  3. If a computed timeout is already <= 0, skip the statement entirely — it is past budget

Example fix

# before
remaining_ms = int((deadline - time.time()) * 1000)  # can be negative
conn.set_query_timeout(remaining_ms)

# after
remaining_ms = int((deadline - time.time()) * 1000)
if remaining_ms <= 0:
    raise TimeoutError("query budget already exhausted")
conn.set_query_timeout(remaining_ms)
Defensive patterns

Strategy: validation

Validate before calling

def safe_query_timeout(conn, ms: int) -> None:
    """0 disables the timeout; negative values are invalid."""
    conn.set_query_timeout(max(0, int(ms)))

Prevention

When it happens

Trigger: `conn.set_query_timeout(-1)` intending "no timeout" (the correct value is 0), or a computed timeout like `int((deadline - time.time()) * 1000)` that goes negative once the deadline passes.

Common situations: Deadline arithmetic racing the clock; request-scoped timeouts derived from already-expired budgets; copying semantics from APIs where -1 means infinite.

Understand the failure class

Related errors


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