Skip to content

feat(cursor): restore SQL executions on a new connection - #95

Open
ClayMav wants to merge 2 commits into
mainfrom
clay/wbc-996-reattach-execution
Open

ClayMav wants to merge 2 commits into
mainfrom
clay/wbc-996-reattach-execution

Conversation

@ClayMav

@ClayMav ClayMav commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Summary

A new client cannot recover a known SQL execution through the public driver API. The driver only reattaches queries already tracked in that process, which blocks MCP's Postgres-backed restart recovery.

Add cursor.attach(execution_id, store=original_store) on a connection to the original SQL session. Fetching and cancellation reuse the existing recovery machinery. Attaching sends retrieve_results with attach, never execute_sql. Missing or cancelled executions preserve their existing errors, and a rejected duplicate attach preserves the current execution and both cursors' result handlers.

Related Issues

WBC-1934, a prerequisite for WBC-996 in Modern MCP architecture.

Verification

  • uv run pytest tests/test_attach.py tests/test_cursor.py tests/test_reconnect.py -q: 140 passed after the duplicate-attach fix, including preservation of pending/completed executions and successful replacement.
  • uv run pytest -q: 349 passed before the final duplicate-attach fix on Python 3.11, with two existing WebSocket deprecation warnings. Covers public attachment to running/completed executions, stored results, cancellation, missing executions, duplicate tracking, invalid IDs, old-session polling, and another transport loss.
  • uv run pre-commit run --all-files: all hooks passed.
  • uv build: source distribution and wheel built successfully.
  • A task-local TLS WebSocket fixture received one execution from a subprocess, which exited abruptly. A new subprocess attached using the saved execution ID and recovered its stored result. The fixture observed exactly one execute_sql and one retrieve_results request with the same ID, with no replay. Both clients, server, and temporary TLS files were cleaned up.

Independent adversarial review of the implementation and duplicate-attach fix found no substantive issues. The fix review independently passed 10 focused tests and an immediate-result check.

On b9763530db0209523fe2dec422392028ec51d765, CI passed on Python 3.10 through 3.14, hosted pre-commit passed, and Salty reviewed with no findings.

No live SQL session, Postgres database, cloud credential, or deployed service was used. Authentication and real server orphan-grace/result-retention behavior need integration qualification. This API neither extends result retention nor provides a durable job store. The remaining MCP work must persist execution identity safely and reconcile uncertain submissions without replaying writes.


Requester Checklist

  • I have self-reviewed my own code
  • I have added/updated tests that prove my fix/feature works
  • I have included visual proof (screenshot, video, or test output) if applicable
  • All CI checks are passing
  • PR size is S/M, OR I have justified the size and added a walkthrough
  • I have updated documentation if needed

Visual Proof

Local restart smoke output:

new client recovered the original stored result
one execution, one attach, no replay after process exit: PASS

Size Justification (if L/XL)

The change includes connection and cursor integration plus lifecycle tests. The cursor resets execution state through one shared method so execute and attach preserve the same result isolation.


Reviewer Checklist

  • Requester checklist above is complete
  • All CI checks are passing
  • Tests adequately cover the changes

Implemented by GPT-6.1 Sol through T3 Code's Codex harness, with an independent agent review.

@ClayMav ClayMav added the please-review-in-depth Changes that need thorough review label Nov 10, 2026
@ClayMav
ClayMav requested a review from a team November 10, 2026 17:07
@ClayMav

ClayMav commented Nov 10, 2026

Copy link
Copy Markdown
Member Author

@salty-hambot review

Comment thread wherobots/db/cursor.py Resolved

@salty-hambot salty-hambot Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed by Salty Hambot 🤖🧂 at b976353

No issues found.
💰 $0.1729 · 146.5k in / 3.1k out tokens · ⏱️ 31.4s · us.openai.gpt-6-sol high, prompt coverage-v1
💬 To request a re-review, comment @salty-hambot review

@ClayMav

ClayMav commented Nov 10, 2026

Copy link
Copy Markdown
Member Author

@salty-hambot review

@ClayMav
ClayMav marked this pull request as ready for review November 10, 2026 17:17
Comment thread README.md
cancel it through that cursor as usual. Attaching sends no SQL, never creates
an execution, and does not extend server result retention. It raises the same
missing/cancelled execution errors described above. A second cursor cannot
attach an execution already tracked by that connection.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: cursor.attach() transfers ownership of a running execution, even if the previous connection is still open: sql-session's _take_ownership() replaces the owner, and later events go to the new connection. the duplicate check only covers cursors on the same connection, so another live connection can attach again and move event ownership back. similar to the above, are we assuming this is the caller's problem to solve?

Comment thread wherobots/db/cursor.py
raise ProgrammingError("This cursor already tracks the execution")
# Preserve the current execution until the new attachment is accepted.
results: queue.Queue[ExecutionResult] = queue.Queue()
attached_id = self.__attach_fn(execution_id, results.put, store)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: sql-session's cancel() looks up the execution by ID without checking whether the requester still owns it. after a recovery worker attaches here, an old worker that still considers the execution pending can reach cursor.close() or reuse its cursor, send cancel, and kill the recovered execution. is the intention that this "ownership" logic will be part of the mcp server's usage of this?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

please-review-in-depth Changes that need thorough review

Development

Successfully merging this pull request may close these issues.

2 participants