Address Copilot findings on per-organisation statement limits (#334, #338) - #342
Conversation
- A row cap whose cap+1 fetch overflows a 32-bit count, and a timeout over seven days, are unusable and fall back to the deployment value (provider and environment alike), instead of failing the statement. - A provider mapping that raises when read falls back instead of escaping. - With a provider registered, tools/list evaluates the visibility predicate in the request task; only the descriptions are offloaded. - Neutral row-limit wording in the execute_sql description; corrected the worker-context comment (both executor copies) and the provider docstring. - Removed the duplicate _STATEMENT_LIMIT_KEYS and the needless pydantic skip. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The timeout skew boundary and related tests/documentation need correction, and an environment-boundary regression test is missing.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR hardens per-organisation SQL statement-limit handling across executor, tooling, and MCP paths.
Changes:
- Adds row-cap and timeout representability checks.
- Hardens provider fallback and visibility filtering.
- Updates regression tests and changelog documentation.
File summaries
| File | Reviewed changes |
|---|---|
tests/test_statement_limit_is_usable.py |
Tests statement-limit boundaries. |
tests/test_per_org_statement_limits.py |
Tests provider fallback, visibility, and timeout limits. |
plugins/agami/lib/execute_sql.py |
Mirrors executor validation changes. |
packages/agami-core/src/tools.py |
Updates provider validation and descriptions. |
packages/agami-core/src/mcp_http.py |
Keeps visibility filtering in the request task. |
packages/agami-core/src/execute_sql.py |
Adds executor representability validation. |
CHANGELOG.md |
Documents the follow-up changes. |
Review details
Suppressed comments (3)
CHANGELOG.md:25
- This documents 604,800 seconds as the largest usable base timeout, but the executor adds a 5-second native skew before sending the setting. If 604,800 is the native maximum, the documented usable base edge is 604,795; please keep this statement aligned with the runtime predicate and tests.
the log. There is no policy ceiling, only what the engines can represent: a time limit over seven
days (604,800 seconds, Snowflake's own maximum and the smallest among the supported engines) and a
row cap of 2,147,483,647 or more (the drivers fetch one row past the cap, in a 32-bit count) are
treated as unusable, from the provider and from `AGAMI_SQL_TIMEOUT_S` / `AGAMI_SQL_MAX_ROWS` alike.
tests/test_per_org_statement_limits.py:493
_LARGEST_ARMABLEis set to 604,800 even though the engine receives that budget plus the 5-second native skew. With a 604,800-second native maximum, the largest accepted base budget is 604,795; otherwise this acceptance test preserves the invalid boundary.
_LARGEST_ARMABLE = 604_800 # seven days: the smallest native maximum among the engines
tests/test_statement_limit_is_usable.py:37
- This edge test encodes the same incorrect boundary as the implementation: the native timeout is armed at
timeout_s + 5, so a base value of 604,800 would request 604,805 and must be rejected when 604,800 is the engine maximum. After tightening the helper, make 604,795 the accepted edge and adjust the next rejected value.
def test_a_timeout_past_seven_days_is_not_usable():
assert tools.statement_limit_is_usable("timeout_s", 604_800) is True
assert tools.statement_limit_is_usable("timeout_s", 604_801) is False
- Files reviewed: 7/7 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| # here would stop the module importing at all rather than failing one call. | ||
| cap = int(raw) if raw.isdecimal() else _DEFAULT_MAX_ROWS | ||
| if cap <= 0: | ||
| if cap <= 0 or not _row_cap_is_representable(cap): |
There was a problem hiding this comment.
Fixed in 45d0db1: test_an_environment_row_cap_the_drivers_cannot_fetch_falls_back sets AGAMI_SQL_MAX_ROWS to 231-1 (falls back to the default) and 231-2 (kept).
| bound fails before the query runs. Seven days is inside every one of those, so it is the one bound | ||
| checked, and a value past it is treated as unusable like any other and falls back to the | ||
| deployment's.""" | ||
| return timeout_s <= 604_800 |
There was a problem hiding this comment.
Fixed in 45d0db1: _timeout_is_representable now checks the native value, timeout_s + _NATIVE_BOUND_SKEW_S <= 604_800, so 604,795 is the largest usable limit (both copies; vendored copy identical). Edge tests moved to 604,795 / 604,796.
| bound fails before the query runs. Seven days is inside every one of those, so it is the one bound | ||
| checked, and a value past it is treated as unusable like any other and falls back to the | ||
| deployment's.""" | ||
| return timeout_s <= 604_800 |
There was a problem hiding this comment.
Fixed in 45d0db1: _timeout_is_representable now checks the native value, timeout_s + _NATIVE_BOUND_SKEW_S <= 604_800, so 604,795 is the largest usable limit (both copies; vendored copy identical). Edge tests moved to 604,795 / 604,796.
…; test the environment row-cap boundary Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Copilot's suppressed comments (CHANGELOG.md:25, tests/test_per_org_statement_limits.py:493, tests/test_statement_limit_is_usable.py:37) — the same off-by-skew boundary — are fixed in 45d0db1: the CHANGELOG now states the native setting (limit + 5 s skew) must stay within 604,800 s, |
Addresses the Copilot review findings left open on two merged PRs: #334 (per-organisation statement limits) and #338 (
statement_limit_is_usable). Includes the comments Copilot left in the collapsed "Suppressed comments" section of its reviews.The owner's decisions, applied throughout:
Findings
tools.py:1934(suppressed)cap + 1rows (fetchmany, psycopg2itersize), so a huge row cap failed the statement withOverflowErrorexecute_sql._row_cap_is_representable: usable only ifcap + 1 <= 2**31 - 1, the C drivers' signed 32-bit count. Applies toAGAMI_SQL_MAX_ROWS(_row_cap_from_env) and to provider values (statement_limit_is_usable). Anything past the bound falls back to the deployment value.execute_sql.py:1651(both copies)statement_timeout) and fail before the query ran_timeout_is_representableis nowtimeout_s <= 604_800(seven days). That is Snowflake's documented maximum forSTATEMENT_TIMEOUT_IN_SECONDSand the smallest among the engines. It also keeps Postgres's int32 milliseconds andthreading.TIMEOUT_MAXsafe. A longer value falls back like any other unusable value, from the provider and fromAGAMI_SQL_TIMEOUT_S.tools.py:1985(suppressed)__bool__/getraises escaped_effective_statement_limitstryas the provider call, usingis not Noneinstead of truthiness. A mapping that raises falls back like a provider that raises.mcp_http.py:510_listedran on a worker thread, including the consumer'svisibilitypredicate_list_toolsnow filters names with_visiblein the request task. Only_described(names)(the provider and descriptions) is sent off the event loop.tools.py:3742execute_sql.py:2708(both copies)mcp_harness.py:88(suppressed)tools/listtesttool_descriptionand the HTTP handler tests.tools.py:1913(suppressed)2**31 - 1or more, timeout over 604,800 s).tools.py:1965_STATEMENT_LIMIT_KEYSwas defined twicetest_statement_limit_is_usable.py:14(suppressed)pytest.importorskip("pydantic")agami-coreinstall without pydantic (33 passed).packages/agami-core/src/execute_sql.pywas copied overplugins/agami/lib/execute_sql.py, and the two are byte-identical (cmp, plus the ACE-093 byte-identity test).Tests
tests/test_statement_limit_is_usable.py: one edge test per limit. Row cap2**31 - 2is usable and2**31 - 1is not. Timeout604_800is usable and604_801is not. The provider-path test also covers both edges.tests/test_per_org_statement_limits.py:main'stools.py/mcp_http.py(ConnectionErrorescapes; the predicate runs off the task) and pass with this change.test_per_org_statement_limits,test_statement_limit_is_usable,test_ace038_timeout,test_ace044_bounded_fetch,test_execute_sql_states_limits,test_ace087_result_bound,test_mcp_http,test_mcp_harnessand the four parity/byte-identity tests. Result: 481 passed, 1 skipped.main, and none of the 39 are in a test file this PR touches.uvx ruff checkis clean on the changed files. No existing file was reformatted; the rewrittentest_statement_limit_is_usable.pypassesruff format --check.🤖 Generated with Claude Code