Skip to content

Commit 4e47382

Browse files
suryaiyer95claude
andcommitted
feat: capture warehouse query failures (binder errors) as \core_failure\
SQL execution errors (e.g. DuckDB \"Binder Error: WHERE clause cannot contain window functions\") were silently swallowed — \`sql.execute\` returns a soft error result so \`core_failure\` never fired. Changes: - Fire \`core_failure\` from \`sql.execute\` error path with masked SQL (string literals replaced with \`?\`, sensitive keys redacted) - Add \`"binder"\`/\`"window function"\` keywords to \`classifyError\` parse_error class so these map correctly instead of falling to \`"unknown"\` - Fix \`categorizeQueryError\` to classify binder/window errors as \`"parse_error"\` instead of \`"other"\` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2e73f2e commit 4e47382

2 files changed

Lines changed: 17 additions & 4 deletions

File tree

packages/opencode/src/altimate/native/connections/register.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ export function detectQueryType(sql: string | null | undefined): string {
166166

167167
export function categorizeQueryError(e: unknown): string {
168168
const msg = String(e).toLowerCase()
169-
if (msg.includes("syntax")) return "syntax_error"
169+
if (msg.includes("binder") || msg.includes("window function") || msg.includes("syntax")) return "parse_error"
170170
if (msg.includes("permission") || msg.includes("denied") || msg.includes("access")) return "permission_denied"
171171
if (msg.includes("timeout")) return "timeout"
172172
if (msg.includes("connection") || msg.includes("closed") || msg.includes("terminated")) return "connection_lost"
@@ -228,6 +228,7 @@ register("sql.execute", async (params: SqlExecuteParams): Promise<SqlExecuteResu
228228
} catch {}
229229
return result
230230
} catch (e) {
231+
const errorMsg = String(e)
231232
try {
232233
Telemetry.track({
233234
type: "warehouse_query",
@@ -239,11 +240,23 @@ register("sql.execute", async (params: SqlExecuteParams): Promise<SqlExecuteResu
239240
duration_ms: Date.now() - startTime,
240241
row_count: 0,
241242
truncated: false,
242-
error: String(e).slice(0, 500),
243+
error: errorMsg.slice(0, 500),
243244
error_category: categorizeQueryError(e),
244245
})
246+
Telemetry.track({
247+
type: "core_failure",
248+
timestamp: Date.now(),
249+
session_id: Telemetry.getContext().sessionId,
250+
tool_name: "sql.execute",
251+
tool_category: "sql",
252+
error_class: Telemetry.classifyError(errorMsg),
253+
error_message: errorMsg.slice(0, 500),
254+
input_signature: Telemetry.computeInputSignature({ sql: params.sql, warehouse: params.warehouse, limit: params.limit }),
255+
masked_args: Telemetry.maskArgs({ sql: params.sql }),
256+
duration_ms: Date.now() - startTime,
257+
})
245258
} catch {}
246-
return { columns: [], rows: [], row_count: 0, truncated: false, error: String(e) } as SqlExecuteResult & { error: string }
259+
return { columns: [], rows: [], row_count: 0, truncated: false, error: errorMsg } as SqlExecuteResult & { error: string }
247260
}
248261
})
249262

packages/opencode/src/altimate/telemetry/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ export namespace Telemetry {
365365
class: Telemetry.Event & { type: "core_failure" } extends { error_class: infer C } ? C : never
366366
keywords: string[]
367367
}> = [
368-
{ class: "parse_error", keywords: ["parse", "syntax", "binder", "unexpected token", "sqlglot"] },
368+
{ class: "parse_error", keywords: ["parse", "syntax", "binder error", "binder", "unexpected token", "sqlglot", "cannot contain window", "window function"] },
369369
{
370370
class: "connection",
371371
keywords: ["econnrefused", "connection", "socket", "enotfound", "econnreset"],

0 commit comments

Comments
 (0)