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.
---
---
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