Skip to content

Bug: PostgresConnector never rolls back after a failed query, poisoning the shared connection for the rest of the process #2669

Description

@P4tk01337

Bug: PostgresConnector never rolls back after a failed query, poisoning the shared connection for the rest of the process

Summary

wren/connector/postgres.py opens a single, long-lived psycopg connection per PostgresConnector instance and never calls .rollback() after a failed query. Since WrenEngine._get_connector() caches this connector for the engine's lifetime, and ServeContext.engine in the MCP server is a single instance shared across the whole server process, one failing query from any client leaves the shared Postgres connection in an aborted-transaction state for every subsequent query from every client, until the process is restarted.

Version

wrenai[postgres]==0.13.2 (latest on PyPI at time of filing)

Where

  • wren/connector/postgres.pyPostgresConnector.__init__ creates one psycopg.connect(...) and stores it as self.connection. query() and dry_run() catch exceptions and re-raise as WrenError, but never call self.connection.rollback().
  • wren/engine.py_get_connector() caches the connector: if self._connector is None: self._connector = get_connector(...), reused for the lifetime of the WrenEngine instance.
  • wren/mcp_server.pyServeContext is documented in-code as "Shared state captured once at startup and used by every tool handler", i.e. one Engine/connector for the whole MCP server process.

Steps to reproduce

  1. Start wren serve mcp against a Postgres data source.
  2. Call run_sql (or dry_run) with SQL that fails at execution time on Postgres itself (not at Wren's SQL-planning stage), e.g. a query referencing a column that doesn't exist:
    SELECT nonexistent_column FROM some_table
    This returns the expected error.
  3. Immediately call run_sql again with a completely valid query, e.g.:
    SELECT COUNT(*) FROM some_table

Expected

Step 3 succeeds normally.

Actual

Step 3 fails with:

[GENERIC_USER_ERROR] current transaction is aborted, commands ignored until end of transaction block

Every subsequent query on that server process fails the same way — from any client, any session — until the container/process is restarted.

Why this matters

Because the connector/engine is shared per-process (not per-request, per-session, or pooled), this isn't a caller-side mistake to recover from — a single bad query from one MCP client degrades the server for every other concurrent client.

Suggested fix

Roll back on failure before re-raising, e.g. in PostgresConnector.query() / dry_run():

except Exception as e:
    try:
        self.connection.rollback()
    except Exception:
        pass
    raise WrenError(...) from e

A more robust fix would move to short-lived connections or an actual connection pool (e.g. psycopg_pool) with reset-on-return semantics, so a single connector instance doesn't hold one Postgres session's transaction state indefinitely.

Environment

  • Reproduced via wren serve mcp --transport http, connecting with an MCP client (mcp Python SDK, streamablehttp_client / ClientSession).
  • Postgres backend (local dev instance).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions