fix(sql): bound query deadlines and propagate session cancellation

Merged
AN
16h ago
+992-204

Issue for this PR

Closes #1483

Stacked on feat/analyst-harness-dab-2026-10 (#1441).

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Why: long-running warehouse queries could outlive the run's work budget (zero meant "no deadline", session cancellation never reached drivers), leaving no time to produce a final answer.

Root cause: per-call/env 0 disabled the query deadline; the run budget phase was the only thing sent to the server, with no query cutoff; sql_execute, sql_check and sql_materialize did not forward session abort; MongoDB cursors, PostgreSQL active statements and SQLite/DuckDB helpers had no abort path.

Before -> after:

  • Zero timeout disabled the deadline -> zero falls through to the configured/default policy (only connection config can disable it).
  • No run-level cutoff -> one absolute query cutoff (reserving min(90s, 20%) of the work budget) is sent with work requests, inherited by child tasks, and clamps every query.
  • Session abort ignored -> abort propagates through sql_execute, sql_check, sql_materialize, sql_explain and value profiling via a shared executeQuery boundary; Mongo cursors close, PostgreSQL clients are discarded, SQLite/DuckDB queued work is rejected and active helpers retired (next statement reports SESSION_STATE_LOST).

Deferred / follow-ups (independent review P2):

  • Local queue wait is charged against the per-query deadline when a run cutoff exists.
  • Mongo native operations (countDocuments, pre-first-batch) do not yet receive the abort signal.
  • PostgreSQL setup-timeout followed by abort can release a client twice.
  • A failed overlapping prompt can clear the active run's cutoff.
  • External runner kill margin, uncancellable remote work (dbt, connectors without cancel protocols) and connection establishment remain governed by their own timeouts.

How did you verify your code works?

  • packages/opencode: touched files plus all test files referencing changed modules (202 files): 7039 pass, 1 fail (PostgreSQL Driver E2E hook timeout, needs a live database). Later re-run of explain/schema tests: 57 pass / 0 fail for sql-explain.
  • packages/drivers: full suite 648 pass, 1 fail (duckdb ... nothing keeps running, counts host helper processes; passes in isolation, 68/68 for local-engine suites, flaky under concurrent runs).
  • bun run typecheck (opencode) and drivers tsgo: clean. git diff --check clean.
  • Upstream markers: analyzer reports no upstream-shared files; additionally audited against the v1 tag, where src/tool/task.ts and src/cli/cmd/run.ts exist, and every added line there is inside altimate_change markers.
  • Independent Codex review: 2 P1 (local abort silently dropped state-loss signal; sql_explain and value profiling bypassed cancellation/cutoff) fixed with regression tests; 4 P2 listed above.

Screenshots / recordings

N/A

Checklist

  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have not introduced any new warnings

🤖 Generated with Claude Code


[!NOTE]
Medium Risk
Touches every SQL execution path, timeout precedence, and driver abort semantics; mis-handled cancellation could discard connections or lose local-engine session state, though behavior is heavily regression-tested.

Overview
Warehouse queries are now bounded by the run’s work budget and can be cancelled end-to-end instead of outliving the session or ignoring abort.

Deadline policy: Per-call or env query_timeout_ms of 0 no longer turns off the deadline—it falls through to connection/env/default (120s). Only connection 0/null disables it. Tool docs and QueryTimeoutError text match that behavior.

Run-level query cutoff: The CLI computes an absolute query deadline from the work budget (reserving min(90s, 20%) for the final answer), sends it on work prompts via x-altimate-run-query-deadline, and registers it per session. Child task sessions inherit the parent cutoff. executeQuery clamps every native/dbt/materialize/check/explain path to the lesser of resolved timeout and remaining cutoff, and wires a merged AbortSignal into drivers.

Driver cancellation: ExecuteOptions.signal is documented for all supporting drivers. SQLite/DuckDB forward abort through LocalEngine (queued work rejected; active helpers SIGKILL’d; next statement gets SESSION_STATE_LOST). PostgreSQL discards pool clients on abort. MongoDB cursor reads race abort/maxTimeMS, close cursors, and use streaming where byte limits apply.

Call-site cleanup: withHardDeadline and per-tool timeout races are replaced by shared executeQuery; dispatcher handlers take optional QueryContext. SQL tools (execute, check, materialize, explain, value profiling) pass ctx.abort and sessionID.

Reviewed by Cursor Bugbot for commit 0328b7c473ea3adbc3d7dea0dcb8716a6c999b96. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Stops long-running warehouse queries from eating the run's work budget and makes session cancellation reach every driver, so there is time to produce a final answer.

  • Per-call and environment 0 deadlines no longer disable the deadline; only connection config (0/null) can.
  • Work requests now carry one absolute query cutoff that reserves min(90s, 20%) of the remaining budget for the answer; child tasks inherit it and every query is clamped by it.
  • Session abort propagates through sql_execute, sql_check, sql_materialize, sql_explain, and value profiling via a shared executeQuery boundary.
  • The drivers cancel on abort: SQLite/DuckDB retire the helper (next statement reports SESSION_STATE_LOST), MongoDB closes active cursors, PostgreSQL discards the client.

Follow-ups (deferred)

  • Local queue wait counts against the per-query deadline when a run cutoff exists.
  • Mongo native operations (countDocuments, pre-first-batch) don't receive the abort signal.
  • PostgreSQL setup-timeout followed by abort can release a client twice.
  • A failed overlapping prompt can clear the active run's cutoff.