Skip to content

Add truncated flag to execute_sql results when max_rows cuts off rows - #406

Merged
tianzhou merged 1 commit into
mainfrom
claude/verdict-issue-404-1aznbv
Aug 21, 2026
Merged

Add truncated flag to execute_sql results when max_rows cuts off rows#406
tianzhou merged 1 commit into
mainfrom
claude/verdict-issue-404-1aznbv

Conversation

@tianzhou

Copy link
Copy Markdown
Member

Summary

Fixes #404 — the max_rows cap fired silently: a capped result reported count equal to max_rows, which is indistinguishable from a table that genuinely has exactly that many rows, so LLM consumers could silently reason from incomplete data.

Now, when the cap actually cuts off rows, the statement's entry in the response carries "truncated": true alongside count. The flag is omitted entirely for complete results (no token cost on the common path), matching how search_objects already signals truncation.

How it works

Detection is exact rather than a count === max_rows heuristic. When the cap is the binding constraint, SQLRowLimiter rewrites the query to fetch one probe row past the cap (LIMIT/TOP max_rows + 1). If the probe row comes back, the connector drops it, clamps rowCount to max_rows, and marks the result set truncated (SQLRowLimiter.flagTruncation). The flag never fires when the query's own smaller LIMIT/TOP is what bounded the result — that's the user's limit, not the cap.

Changes

  • src/utils/sql-row-limiter.ts: new applyMaxRowsWithTruncationProbe / applyMaxRowsForSQLServerWithTruncationProbe (built on the existing rewrite logic) and flagTruncation post-processing helper
  • src/connectors/interface.ts: optional truncated field on SQLResultSet
  • All five connectors (PostgreSQL, MySQL, MariaDB, SQLite, SQL Server) wire the probe through executeSQL, including per-statement flags in multi-statement batches
  • src/utils/tool-handler-helpers.ts: toStatementsPayload emits truncated: true when set (shared by execute_sql and custom tools)
  • src/utils/tool-metadata.ts: the tool description's row-limit note now tells consumers capped results carry "truncated": true
  • SQL Server now echoes the original statement text in sql instead of the TOP-rewritten one, matching the other connectors (and not leaking the probe value)
  • Docs: docs/tools/execute-sql.mdx row-limiting section documents the flag

Test plan

  • New unit tests for the probe rewrites and flagTruncation (sql-row-limiter.test.ts, 59 tests passing)
  • New handler tests: payload carries truncated: true when set, omits the key for complete results (execute-sql.test.ts)
  • New SQLite integration tests: capped vs exactly-max_rows vs fewer rows vs user's own lower LIMIT vs per-statement in multi-statement batches (49 tests passing locally)
  • truncated assertions added to the existing maxRows integration tests for PostgreSQL, MySQL, MariaDB, SQL Server (Docker-based; will run in CI)
  • Full unit suite passes (981 tests), pnpm run build:backend clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01SxUnGXE7MRMyezpunumuFH


Generated by Claude Code

When a configured max_rows cap fired, the tool response was
indistinguishable from a table that genuinely has exactly max_rows rows
(count simply equaled the cap), so LLM consumers could silently reason
from incomplete data.

Detection is exact rather than heuristic: when the cap is the binding
constraint, SQLRowLimiter rewrites the query to fetch max_rows + 1 rows
(LIMIT/TOP probe). If the probe row comes back, the connector drops it,
clamps the count to max_rows, and marks the result set truncated; the
statements payload then carries "truncated": true alongside count. The
flag is omitted entirely for complete results, and never fires when the
query's own smaller LIMIT/TOP is what bounded the result.

Applies to all five connectors (PostgreSQL, MySQL, MariaDB, SQLite,
SQL Server), including per-statement flags in multi-statement batches.
SQL Server now also echoes the original statement text instead of the
TOP-rewritten one, matching the other connectors.

Closes #404

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SxUnGXE7MRMyezpunumuFH
Copilot AI lite review requested due to automatic review settings August 21, 2026 04:26

Copilot AI left a comment

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.

Pull request overview

This PR addresses silent max_rows truncation in execute_sql responses by adding an explicit truncated: true signal when the configured cap actually cuts off rows, enabling tool consumers to distinguish capped results from complete results that happen to have exactly max_rows rows.

Changes:

  • Add truncation-probe rewrites (max_rows + 1) and post-processing (flagTruncation) to set SQLResultSet.truncated only when truncation is provably occurring.
  • Propagate the optional truncated field through connector result sets and into the tool response payload (statements[].truncated), while omitting the key for complete results.
  • Update docs, tool metadata text, and tests (unit, handler, and connector integration) to cover capped vs non-capped scenarios.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/utils/tool-metadata.ts Updates execute tool description to document the truncated: true signal when max_rows caps results.
src/utils/tool-handler-helpers.ts Extends statements payload shaping to conditionally emit truncated: true only when present on a result set.
src/utils/sql-row-limiter.ts Introduces probe-based max-rows rewrites and a flagTruncation helper to clamp/drop probe rows and mark truncation.
src/utils/tests/sql-row-limiter.test.ts Adds unit coverage for probe rewrites and flagTruncation behavior.
src/tools/tests/execute-sql.test.ts Adds handler-level tests verifying truncated is surfaced when set and omitted otherwise.
src/connectors/sqlserver/index.ts Wires SQL Server execution through probe rewrite + flagTruncation, and ensures sql echoes the original statement (not the rewritten TOP form) when attributable.
src/connectors/sqlite/index.ts Wires SQLite single- and multi-statement read execution through probe rewrite + flagTruncation.
src/connectors/postgres/index.ts Wires Postgres single- and multi-statement execution through probe rewrite + per-statement flagTruncation.
src/connectors/mysql/index.ts Wires MySQL multi-statement execution through per-statement probe rewrites and applies flagTruncation when result-set alignment is exact.
src/connectors/mariadb/index.ts Wires MariaDB multi-statement execution through per-statement probe rewrites and applies flagTruncation when result-set alignment is exact.
src/connectors/interface.ts Extends SQLResultSet with optional truncated?: boolean and documents its exact/probe-based semantics.
src/connectors/tests/sqlserver.integration.test.ts Adds integration assertions for SQL Server truncation flag behavior under cap vs user TOP.
src/connectors/tests/sqlite.integration.test.ts Adds integration coverage for capped vs exact-vs-fewer rows and multi-statement truncation signaling in SQLite.
src/connectors/tests/postgres.integration.test.ts Adds integration assertions for Postgres truncation flag behavior under cap vs user LIMIT.
src/connectors/tests/mysql.integration.test.ts Adds integration assertions for MySQL truncation flag behavior under cap vs user LIMIT.
src/connectors/tests/mariadb.integration.test.ts Adds integration assertions for MariaDB truncation flag behavior under cap vs user LIMIT.
docs/tools/execute-sql.mdx Documents the truncated: true field and how to interpret it (including using COUNT(*) for true totals).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@tianzhou
tianzhou merged commit 92061ea into main Aug 21, 2026
4 checks passed
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.

max_rows cap fires silently — no truncation signal in tool response

3 participants