From 50d10443d1a977c41a1f8b011f4a7dd8a40211f8 Mon Sep 17 00:00:00 2001 From: ZdenekSrotyr <139972147+ZdenekSrotyr@users.noreply.github.com> Date: Thu, 7 May 2026 17:24:10 +0200 Subject: [PATCH] =?UTF-8?q?release:=200.46.2=20=E2=80=94=20friendlier=20hi?= =?UTF-8?q?nt=20on=20missing-table=20errors=20for=20remote=20tables=20(#21?= =?UTF-8?q?9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary `agnes query "DESCRIBE unit_economics"` (where `unit_economics` is `query_mode='remote'`) previously returned DuckDB's nearest-name suggestion (`Did you mean "order_economics"`?), sending users down the wrong path. Now appends a friendly hint about remote tables. Reproduced from a real analyst session — colleague spent ~30s diagnosing what was actually "this is a remote table, not materialized locally". ## Test plan - [x] New test: `_query_local("DESCRIBE unit_economics", ...)` against an empty local DuckDB triggers the new hint, original DuckDB error still echoed. - [x] Negative test: a syntax-error query does NOT trigger the hint (regex only matches "Table with name X does not exist"). - [x] `pytest tests/test_cli_query*.py` clean. --- Open in Devin Review --- CHANGELOG.md | 6 ++++++ cli/commands/query.py | 21 ++++++++++++++++++++ pyproject.toml | 2 +- tests/test_cli_query.py | 43 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68acdc9..99878ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,12 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C ## [Unreleased] +## [0.46.2] — 2026-05-07 + +### Fixed + +- `agnes query` against a `query_mode='remote'` table previously surfaced DuckDB's misleading "did you mean " suggestion. Now appends a friendlier hint pointing users to `agnes catalog`, `agnes schema `, and `agnes query --remote`. Reproduces from a real analyst session where `DESCRIBE unit_economics` (a remote table) sent the user down a 30-second wrong path. + ## [0.46.1] — 2026-05-07 ### Fixed diff --git a/cli/commands/query.py b/cli/commands/query.py index 5524e56..b57b101 100644 --- a/cli/commands/query.py +++ b/cli/commands/query.py @@ -2,6 +2,7 @@ import json import os +import re import sys from pathlib import Path from typing import List, Optional @@ -78,6 +79,26 @@ def _query_local(sql: str, fmt: str, limit: int): _output(columns, result, fmt) except Exception as e: typer.echo(f"Query error: {e}", err=True) + # DuckDB's "Did you mean " suggestion is + # misleading when the unresolvable identifier is actually a + # `query_mode='remote'` table — those have no local view by design. + # Append a friendly hint pointing the user at `agnes catalog`, + # `agnes schema`, and `agnes query --remote`. We don't verify against + # the remote registry here (this command is offline-friendly), so the + # hint is conditional ("might be") — safe even when the name was just + # a typo. + m = re.search(r"Table with name ([A-Za-z_][A-Za-z0-9_]*) does not exist", str(e)) + if m: + typer.echo("", err=True) + typer.echo( + f"Note: `{m.group(1)}` might be a `query_mode='remote'` table. Local " + "DuckDB only holds views for `local` and `materialized` tables — " + "`remote` ones live on BigQuery and are not synced.\n" + " - List all registered tables: agnes catalog\n" + " - Inspect column schema: agnes schema \n" + " - Run a query against BigQuery: agnes query --remote \"\"", + err=True, + ) raise typer.Exit(1) finally: conn.close() diff --git a/pyproject.toml b/pyproject.toml index faa1c3a..d585a5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "agnes-the-ai-analyst" -version = "0.46.1" +version = "0.46.2" description = "Agnes — AI Data Analyst platform for AI analytical systems" requires-python = ">=3.11,<3.14" license = "MIT" diff --git a/tests/test_cli_query.py b/tests/test_cli_query.py index 2af1bd7..db9fe51 100644 --- a/tests/test_cli_query.py +++ b/tests/test_cli_query.py @@ -150,3 +150,46 @@ class TestLocalQuery: result = runner.invoke(app, ["query", "SELECT * FROM nonexistent_table_xyz"]) assert result.exit_code == 1 assert "Query error" in result.output + + def test_local_query_missing_table_hints_remote(self, tmp_config): + """Querying a table absent from local DuckDB surfaces a hint about + `query_mode='remote'` tables alongside the original DuckDB error. + + Reproduces the analyst-session UX gap where DuckDB's nearest-name + ("Did you mean ") suggestion sent the user down the + wrong path — they thought the table didn't exist or they typo'd, + when in fact it's a remote table that intentionally has no local + view. + """ + import duckdb + db_dir = tmp_config / "local" / "user" / "duckdb" + db_dir.mkdir(parents=True) + duckdb.connect(str(db_dir / "analytics.duckdb")).close() + + result = runner.invoke(app, ["query", "DESCRIBE unit_economics"]) + assert result.exit_code == 1 + # Original DuckDB diagnostic must remain visible (don't break logging). + assert "Query error" in result.output + assert "Table with name unit_economics does not exist" in result.output + # New hint fires. + assert "query_mode='remote'" in result.output + assert "agnes catalog" in result.output + assert "agnes schema" in result.output + assert "agnes query --remote" in result.output + + def test_local_query_syntax_error_does_not_show_remote_hint(self, tmp_config): + """A non-missing-table failure (e.g. raw syntax error) must NOT + trigger the new remote-mode hint — the regex only matches DuckDB's + `Table with name X does not exist` shape. + """ + import duckdb + db_dir = tmp_config / "local" / "user" / "duckdb" + db_dir.mkdir(parents=True) + duckdb.connect(str(db_dir / "analytics.duckdb")).close() + + # Trailing FROM with no relation -> ParserException, not CatalogException. + result = runner.invoke(app, ["query", "SELECT * FROM"]) + assert result.exit_code == 1 + assert "Query error" in result.output + assert "query_mode='remote'" not in result.output + assert "agnes query --remote" not in result.output