Skip to content

fix: stop crashing and losing writes when a pool hook leaves a transaction open - #48

Merged
jnsdls merged 1 commit into
mainfrom
fix/sqlalchemy-intrans-checkout
Oct 28, 2026
Merged

jnsdls merged 1 commit into
mainfrom
fix/sqlalchemy-intrans-checkout

Conversation

@jnsdls

@jnsdls jnsdls commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Summary

A pool hook that runs a statement through the driver (a SQLAlchemy checkout listener, a psycopg-pool check) hands the connection out INTRANS: in non-autocommit mode the driver opened a transaction for that statement. Found upgrading Nebula to 0.0.4 (agent-labs-dev/nebula#11193), whose _on_checkout runs SELECT 1; SET statement_timeout ….

What broke on such a connection, by path:

path sqlalchemy (asyncpg) sqlalchemy (psycopg) psycopg pool
bare read (ZQLDatabase.run, 0.0.4) hook's tx silently rolled back: its SETs lost ProgrammingError: can't change 'autocommit' now same ProgrammingError
transaction / push session session: AssertionError in _send_begin ok session: transaction() is only a savepoint, pool rolls the writes back: mutation lost
any transaction with isolation_level SET TRANSACTION ISOLATION LEVEL must be called before any query (after the hook's SELECT 1) same same

Fix: every adapter path that takes a connection first commits a transaction the pool left open, keeping what the hook set, then carries on unchanged. An IDLE connection costs a local status check, no round trip, so bare reads still go out with no BEGIN/COMMIT.

 SQLAlchemyConnection.query / transaction / _transaction (session)
   engine.connect()
+  _settle(connection)      # driver INTRANS? commit it
   AUTOCOMMIT | begin()
 PsycopgConnection.query / _transaction (session)
   pool.connection() | getconn()
+  _settle(connection)
   set_autocommit(True) | connection.transaction()

asyncpg direct: unaffected. asyncpg opens no implicit transactions, so a pool setup/init leaves one open only by sending BEGIN itself.

Evidence

New differential tests in tests/wire/test_mutate.py. The hooked engine or pool runs SELECT 1 then SET statement_timeout = '4321ms' on every checkout:

test_run_matches_upstream_on_a_connection_checked_out_in_a_transaction[sqlalchemy|sqlalchemy-psycopg|psycopg]
  recordedRun (bare) == upstream's, and SHOW statement_timeout == 4321ms
test_transactions_match_upstream_on_a_connection_checked_out_in_a_transaction[None|serializable × 3 adapters]
  transaction alone and in a session: isolation == upstream's, statement_timeout == 4321ms, both writes committed
  • Before: 8 failed, 1 passed (sqlalchemy-psycopg transaction with no isolation, the 0.0.3 path)
  • After: 9 passed. Full pytest 795 passed, tests.compat complete, ruff and ty clean.

Merge Danger

Door: two-way

Blast Radius: adapters

Only connections handed out INTRANS behave differently: the hook's transaction now commits up front. Before, it crashed, rolled back, or dropped writes. One COMMIT round trip per checkout in that case.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

…sing it

A pool hook that runs a statement through the driver (a SQLAlchemy
`checkout` listener, a psycopg-pool `check`) leaves the connection in a
transaction. Bare reads then crashed on psycopg switching to autocommit,
silently rolled the hook's settings back on asyncpg, a SQLAlchemy+asyncpg
session tripped an assertion, a psycopg session's writes were rolled back,
and isolation levels could not be set after the hook's SELECT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codspeed

codspeed Bot commented Oct 28, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 46 untouched benchmarks


Comparing fix/sqlalchemy-intrans-checkout (2a244c2) with main (1e2b8da)

Open in CodSpeed

@jnsdls
jnsdls merged commit 5dee9d8 into main Oct 28, 2026
9 checks passed
@jnsdls
jnsdls deleted the fix/sqlalchemy-intrans-checkout branch October 28, 2026 23:14
@jnsdls jnsdls mentioned this pull request Oct 28, 2026
jnsdls added a commit that referenced this pull request Oct 28, 2026
Bumps the version to 0.0.5 in `pyproject.toml`, `uv.lock` and
`zero_server.__version__`. Nothing else changes.

Since 0.0.4, `main` has gained one fix (#48): the SQLAlchemy and psycopg
adapters no longer crash or lose writes when a pool hook hands a
connection out with a transaction open.

After merge, tag `v0.0.5` on main to publish to PyPI.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/agent-labs-dev/codesmith/zero-python/pr/49?autoLogin=true&ref=codesmith_pr_footer"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v3.svg"><img
alt="View with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v3-3x.png"
width="162" height="28"></picture></a> <a
href="https://backend.blacksmith.sh/track/enable-autofix?expires=1795821283&installation_model_id=434381&pr_number=49&ref=codesmith_pr_footer&repository=agent-labs-dev%2Fzero-python&return_to=https%3A%2F%2Fgithub.com%2Fagent-labs-dev%2Fzero-python%2Fpull%2F49&signature=c4ff320bb210d2e594509c31c900b17e7705df1a20fc139e77115568cf6bb5a8"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img
alt="Autofix with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png"
width="190" height="28"></picture></a>
<sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you
need.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant