You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
SQL read/write classifier fails closed on valid reads (//, typed TIMESTAMPTZ literals, NOT GLOB, CREATE+SELECT) and reports a misleading "user rule" error #1488
When the parser cannot read a statement, classifyAndCheck classifies it as a write. The analyst config denies writes, and the model sees only "The user has specified a rule which prevents you from using this specific tool call". Nothing says the statement was actually a parse failure on a valid read, so models rewrite blindly or switch to slower workarounds.
Mechanism
classifyAndCheck falls back to classifyFallback, which returns "write" when the parser throws (packages/opencode/src/altimate/tools/sql-classify.ts:131, :172-193). sql_execute then asks for sql_execute_write (sql-execute.ts:113-121), which the analyst agent denies (packages/opencode/src/agent/agent.ts:254).
isScratchSafeWrite requires every statement to be a write (sql-classify.ts:226-271), so CREATE TABLE ... AS SELECT ...; SELECT ... in one call is refused.
Parser gaps on pure reads (probed against the bundled parser): DuckDB integer division //; SQLite NOT GLOB; FILTER (...) variants; alias names at and match; PIVOT; typed literals such as TIMESTAMPTZ '2023-05-02 00:00:00+00'. The same queries with /, or with ::timestamptz casts, classify as reads.
Evidence
17 of 17 queries containing // were denied; 4 containing GLOB.
90 refusals of pure SELECT/WITH statements in one set of sessions: 87 contain a typed TIMESTAMPTZ '...' literal, 0 of 2,676 allowed queries on the same warehouses do (inferred from this split; not yet reproduced in a unit test).
31 denied scratch CREATE ... AS SELECT batches with a trailing SELECT.
28 of 32 denials in one set of sessions were avoidable, at about 3.4-3.8 s per lost turn (about 600 s total); 23 generations had every call refused.
The refusals pushed models onto a scratch-to-scratch sql_materialize path (45 calls, 728 s, including 4 helper crashes) and blocked a session-setting workaround for expression depth limits (see the many-table issue).
Impact
Time and cost; no direct answer loss (affected sessions recovered), but the risk of abandoning a correct approach is real.
Proposed fix
Report parser failures as their own message: "could not prove this is read-only: parser error at col N; rewrite or split the statement".
When the parse fails but the statement starts with SELECT/WITH and masked code has no write keyword, run on a read-only connection.
Accept scratch-safe writes followed by reads and return the last result set.
Acceptance check
Regression tests classifying each of the listed constructs as read, a CREATE ...; SELECT ... scratch batch accepted, and a snapshot of the new denial message for a genuinely unparseable statement.
Activity
anandgupta42 commentedon Nov 5, 2026
Consolidated into #1679, which tracks this together with related issues. The original report here stays linked from that tracker.