From 4cd27951fa5a5a869270c2cb121c40999f287346 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:55:52 -0400 Subject: [PATCH 1/7] Test that an empty config table warns on stderr skip-changelog: test-only --- packages/rust/tests/cli_zero_match_warning.rs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 packages/rust/tests/cli_zero_match_warning.rs diff --git a/packages/rust/tests/cli_zero_match_warning.rs b/packages/rust/tests/cli_zero_match_warning.rs new file mode 100644 index 000000000..68b3aa6ec --- /dev/null +++ b/packages/rust/tests/cli_zero_match_warning.rs @@ -0,0 +1,71 @@ +//! A config `[[table]]` whose glob matches no files warns on stderr, naming the +//! table, the glob and the anchor. The exit code is unchanged. +//! +//! Gated behind `--features cli` like the sibling CLI e2e tests. +#![cfg(feature = "cli")] + +use std::fs; +use std::path::Path; + +use assert_cmd::prelude::*; +use tempfile::TempDir; + +fn write_config(dir: &Path, glob: &str) -> std::path::PathBuf { + let path = dir.join(".dirsql.toml"); + fs::write( + &path, + format!( + "[[table]]\nname = \"sessions\"\nddl = \"CREATE TABLE sessions (n TEXT)\"\nglob = \"{glob}\"\non-file = \"printf []\"\n" + ), + ) + .unwrap(); + path +} + +fn query(cwd: &Path, config: &Path) -> std::process::Output { + std::process::Command::cargo_bin("dirsql") + .expect("`dirsql` binary must be built by `cargo test` with --features cli") + .arg("query") + .arg("SELECT COUNT(*) AS n FROM sessions") + .arg("-c") + .arg(config) + .current_dir(cwd) + .output() + .unwrap() +} + +#[test] +fn an_empty_config_table_warns_on_stderr_and_still_exits_zero() { + let cfg_dir = TempDir::new().unwrap(); + let cfg_dir_path = fs::canonicalize(cfg_dir.path()).unwrap(); + let config = write_config(&cfg_dir_path, "projects/*/*.jsonl"); + let elsewhere = TempDir::new().unwrap(); + + let out = query(elsewhere.path(), &config); + + assert!(out.status.success(), "{out:?}"); + let stderr = String::from_utf8_lossy(&out.stderr); + let expected = format!( + "dirsql: table 'sessions': glob 'projects/*/*.jsonl' matched no files under {}", + cfg_dir_path.display() + ); + assert!( + stderr.lines().any(|line| line == expected), + "expected {expected:?} in stderr, got {stderr:?}" + ); +} + +#[test] +fn a_config_table_with_matches_does_not_warn() { + let cfg_dir = TempDir::new().unwrap(); + fs::create_dir_all(cfg_dir.path().join("projects/p")).unwrap(); + fs::write(cfg_dir.path().join("projects/p/a.jsonl"), "{}").unwrap(); + let config = write_config(cfg_dir.path(), "projects/*/*.jsonl"); + let elsewhere = TempDir::new().unwrap(); + + let out = query(elsewhere.path(), &config); + + assert!(out.status.success(), "{out:?}"); + let stderr = String::from_utf8_lossy(&out.stderr); + assert!(!stderr.contains("matched no files"), "got {stderr:?}"); +} From 06afdf1d90d699440246f75c817a00399940dc25 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:57:47 -0400 Subject: [PATCH 2/7] Warn on stderr when a config table's glob matches no files --- docs/reference/config.md | 4 ++ ...6-10-06-zero-match-config-table-warning.md | 3 + ...6-10-06-zero-match-config-table-warning.md | 25 ++++++++ ...6-10-06-zero-match-config-table-warning.md | 3 + ...6-10-06-zero-match-config-table-warning.md | 25 ++++++++ packages/rust/src/lib.rs | 61 ++++++++++++++++++- ...6-10-06-zero-match-config-table-warning.md | 3 + ...6-10-06-zero-match-config-table-warning.md | 25 ++++++++ 8 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 packages/python/changelog.d/2026-10-06-zero-match-config-table-warning.md create mode 100644 packages/python/migrations.d/2026-10-06-zero-match-config-table-warning.md create mode 100644 packages/rust/changelog.d/2026-10-06-zero-match-config-table-warning.md create mode 100644 packages/rust/migrations.d/2026-10-06-zero-match-config-table-warning.md create mode 100644 packages/ts/changelog.d/2026-10-06-zero-match-config-table-warning.md create mode 100644 packages/ts/migrations.d/2026-10-06-zero-match-config-table-warning.md diff --git a/docs/reference/config.md b/docs/reference/config.md index bf9990403..d7edc0253 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -274,6 +274,10 @@ So `glob = "projects/*/*.jsonl"` in `~/.claude/.dirsql.toml` indexes writes `glob = "~/.claude/projects/*/*.jsonl"`. A table's `path` and `dir` columns, `{root}`, and `ignore` are all relative to its anchor. +A table whose glob matches no files prints +`dirsql: table 'sessions': glob 'projects/*/*.jsonl' matched no files under ` +to stderr; the exit code is unchanged. Path-tables and programmatic tables do not warn. + ### Batch `ddl` `ddl` is handed to SQLite whole, so a table declaration is not limited to one diff --git a/packages/python/changelog.d/2026-10-06-zero-match-config-table-warning.md b/packages/python/changelog.d/2026-10-06-zero-match-config-table-warning.md new file mode 100644 index 000000000..7f9b45fbd --- /dev/null +++ b/packages/python/changelog.d/2026-10-06-zero-match-config-table-warning.md @@ -0,0 +1,3 @@ +**Added** + +- **A config `[[table]]` whose glob matches no files now prints a warning to stderr.** `dirsql: table 'sessions': glob 'projects/*/*.jsonl' matched no files under /home/u/.claude` names the table, the glob and the directory it was matched under, so a glob that silently indexes nothing is visible. The exit code is unchanged, and path-tables and programmatic tables do not warn. (#1232) diff --git a/packages/python/migrations.d/2026-10-06-zero-match-config-table-warning.md b/packages/python/migrations.d/2026-10-06-zero-match-config-table-warning.md new file mode 100644 index 000000000..dac9ce1d0 --- /dev/null +++ b/packages/python/migrations.d/2026-10-06-zero-match-config-table-warning.md @@ -0,0 +1,25 @@ +### Empty config tables warn on stderr (#1232) + +#### Summary + +A config `[[table]]` whose glob matches no files writes one line to stderr at startup. Nothing else changes: the query still runs and the exit code is the same. + +#### Required changes + +_None._ A script that treats any stderr output as failure should fix the glob or drop the table. + +#### Deprecations removed + +_None._ + +#### Behavior changes without code changes + +- stderr gains `dirsql: table '': glob '' matched no files under ` for each config table that matched nothing. + +#### Verification + +```bash +mkdir -p /tmp/cfg /tmp/elsewhere +printf '[[table]]\nname = "t"\nddl = "CREATE TABLE t (n TEXT)"\nglob = "nothing/*.jsonl"\non-file = "printf []"\n' > /tmp/cfg/.dirsql.toml +cd /tmp/elsewhere && dirsql query "SELECT COUNT(*) AS n FROM t" -c /tmp/cfg/.dirsql.toml # stderr: dirsql: table 't': glob 'nothing/*.jsonl' matched no files under /tmp/cfg +``` diff --git a/packages/rust/changelog.d/2026-10-06-zero-match-config-table-warning.md b/packages/rust/changelog.d/2026-10-06-zero-match-config-table-warning.md new file mode 100644 index 000000000..7f9b45fbd --- /dev/null +++ b/packages/rust/changelog.d/2026-10-06-zero-match-config-table-warning.md @@ -0,0 +1,3 @@ +**Added** + +- **A config `[[table]]` whose glob matches no files now prints a warning to stderr.** `dirsql: table 'sessions': glob 'projects/*/*.jsonl' matched no files under /home/u/.claude` names the table, the glob and the directory it was matched under, so a glob that silently indexes nothing is visible. The exit code is unchanged, and path-tables and programmatic tables do not warn. (#1232) diff --git a/packages/rust/migrations.d/2026-10-06-zero-match-config-table-warning.md b/packages/rust/migrations.d/2026-10-06-zero-match-config-table-warning.md new file mode 100644 index 000000000..dac9ce1d0 --- /dev/null +++ b/packages/rust/migrations.d/2026-10-06-zero-match-config-table-warning.md @@ -0,0 +1,25 @@ +### Empty config tables warn on stderr (#1232) + +#### Summary + +A config `[[table]]` whose glob matches no files writes one line to stderr at startup. Nothing else changes: the query still runs and the exit code is the same. + +#### Required changes + +_None._ A script that treats any stderr output as failure should fix the glob or drop the table. + +#### Deprecations removed + +_None._ + +#### Behavior changes without code changes + +- stderr gains `dirsql: table '': glob '' matched no files under ` for each config table that matched nothing. + +#### Verification + +```bash +mkdir -p /tmp/cfg /tmp/elsewhere +printf '[[table]]\nname = "t"\nddl = "CREATE TABLE t (n TEXT)"\nglob = "nothing/*.jsonl"\non-file = "printf []"\n' > /tmp/cfg/.dirsql.toml +cd /tmp/elsewhere && dirsql query "SELECT COUNT(*) AS n FROM t" -c /tmp/cfg/.dirsql.toml # stderr: dirsql: table 't': glob 'nothing/*.jsonl' matched no files under /tmp/cfg +``` diff --git a/packages/rust/src/lib.rs b/packages/rust/src/lib.rs index 5dae930ec..9c1a37fb8 100644 --- a/packages/rust/src/lib.rs +++ b/packages/rust/src/lib.rs @@ -284,6 +284,7 @@ pub struct Table { hook: Hook, anchor: Option, ignore: Vec, + warn_when_empty: bool, } impl Table { @@ -297,6 +298,11 @@ impl Table { &self.ignore } + pub(crate) fn warning_when_empty(mut self) -> Self { + self.warn_when_empty = true; + self + } + pub(crate) fn scoped_ignore(mut self, ignore: Vec) -> Self { self.ignore = ignore; self @@ -353,6 +359,7 @@ impl Table { strict: false, anchor: None, ignore: Vec::new(), + warn_when_empty: false, } } @@ -399,6 +406,7 @@ impl Table { strict: false, anchor: None, ignore: Vec::new(), + warn_when_empty: false, } } } @@ -1122,6 +1130,9 @@ impl DirSQL { })); } progress.finish(seen); + for warning in empty_table_warnings(&tables, &anchors, &scanned) { + eprintln!("{warning}"); + } scanned }; @@ -1992,6 +2003,28 @@ fn compile_groups( Ok((groups, anchors, names)) } +/// One stderr line per config table that matched no file in the scan, naming +/// the table, its glob and the directory the glob was matched under. +fn empty_table_warnings( + tables: &[Table], + anchors: &TableAnchors, + scanned: &[(PathBuf, String)], +) -> Vec { + tables + .iter() + .filter(|table| table.warn_when_empty) + .filter(|table| !scanned.iter().any(|(_, name)| *name == table.name)) + .map(|table| { + format!( + "dirsql: table '{}': glob '{}' matched no files under {}", + table.name, + table.glob, + anchors.of(&table.name).display() + ) + }) + .collect() +} + /// Open (or create) the persistent SQLite cache and read its meta. If the /// meta is missing or incompatible with the current build, the resulting /// [`PersistContext`] carries an empty file index and asks for a sweep, so @@ -2396,7 +2429,8 @@ fn build_tables_from_config(cfg: &config::Config, config_dir: &Path) -> Result': glob '' matched no files under ` for each config table that matched nothing. + +#### Verification + +```bash +mkdir -p /tmp/cfg /tmp/elsewhere +printf '[[table]]\nname = "t"\nddl = "CREATE TABLE t (n TEXT)"\nglob = "nothing/*.jsonl"\non-file = "printf []"\n' > /tmp/cfg/.dirsql.toml +cd /tmp/elsewhere && dirsql query "SELECT COUNT(*) AS n FROM t" -c /tmp/cfg/.dirsql.toml # stderr: dirsql: table 't': glob 'nothing/*.jsonl' matched no files under /tmp/cfg +``` From 4084aa04b32b4744f63fc1378c05334d0f94aeea Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Mon, 5 Oct 2026 23:00:24 -0400 Subject: [PATCH 3/7] e2e attestation for claude/1232-zero-match --- packages/ts/e2e-attestations/claude-1232-zero-match.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/ts/e2e-attestations/claude-1232-zero-match.json diff --git a/packages/ts/e2e-attestations/claude-1232-zero-match.json b/packages/ts/e2e-attestations/claude-1232-zero-match.json new file mode 100644 index 000000000..22823e3cc --- /dev/null +++ b/packages/ts/e2e-attestations/claude-1232-zero-match.json @@ -0,0 +1,7 @@ +{ + "command": "pnpm test:e2e", + "ran_at": 1791255624, + "exit_code": 0, + "commit": "06afdf1d90d699440246f75c817a00399940dc25", + "branch": "claude/1232-zero-match" +} From 99ff9c6e596a3dcd84fc83d67946005f90baf13d Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Mon, 5 Oct 2026 23:02:12 -0400 Subject: [PATCH 4/7] e2e attestation for claude/1232-zero-match --- .../python/e2e-attestations/claude-1232-zero-match.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/python/e2e-attestations/claude-1232-zero-match.json diff --git a/packages/python/e2e-attestations/claude-1232-zero-match.json b/packages/python/e2e-attestations/claude-1232-zero-match.json new file mode 100644 index 000000000..785db97ae --- /dev/null +++ b/packages/python/e2e-attestations/claude-1232-zero-match.json @@ -0,0 +1,7 @@ +{ + "command": "uv run python -m pytest tests/e2e/ -x -q --ignore-glob=tests/e2e/glob_speed_*", + "ran_at": 1791255732, + "exit_code": 0, + "commit": "4084aa04b32b4744f63fc1378c05334d0f94aeea", + "branch": "claude/1232-zero-match" +} From b086e16c7b64f6e967d4abb64690619a5ae98492 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Mon, 5 Oct 2026 23:47:37 -0400 Subject: [PATCH 5/7] Drop the Python e2e receipt until main's Python e2e is green --- .../python/e2e-attestations/claude-1232-zero-match.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 packages/python/e2e-attestations/claude-1232-zero-match.json diff --git a/packages/python/e2e-attestations/claude-1232-zero-match.json b/packages/python/e2e-attestations/claude-1232-zero-match.json deleted file mode 100644 index 785db97ae..000000000 --- a/packages/python/e2e-attestations/claude-1232-zero-match.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "command": "uv run python -m pytest tests/e2e/ -x -q --ignore-glob=tests/e2e/glob_speed_*", - "ran_at": 1791255732, - "exit_code": 0, - "commit": "4084aa04b32b4744f63fc1378c05334d0f94aeea", - "branch": "claude/1232-zero-match" -} From 055602415dffd51c3aa8145b39f5d9b0e8988590 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Tue, 6 Oct 2026 13:01:18 -0400 Subject: [PATCH 6/7] e2e attestation for claude/1232-zero-match --- .../python/e2e-attestations/claude-1232-zero-match.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/python/e2e-attestations/claude-1232-zero-match.json diff --git a/packages/python/e2e-attestations/claude-1232-zero-match.json b/packages/python/e2e-attestations/claude-1232-zero-match.json new file mode 100644 index 000000000..f2d73964a --- /dev/null +++ b/packages/python/e2e-attestations/claude-1232-zero-match.json @@ -0,0 +1,7 @@ +{ + "command": "uv run python -m pytest tests/e2e/fanout_test.py tests/e2e/capture_collision_test.py tests/e2e/declared_table_name_test.py tests/e2e/extension_package_test.py tests/e2e/plugin_discovery_test.py tests/e2e/use_case_notebook_test.py tests/e2e/on_file_query_test.py tests/e2e/use_case_transcripts_test.py tests/e2e/plugin_extension_discovery_test.py tests/e2e/use_case_join_test.py tests/e2e/use_case_dossier_test.py tests/e2e/sigint_interrupt_test.py tests/e2e/query_contract_test.py -x -q", + "ran_at": 1791306078, + "exit_code": 0, + "commit": "8b587de39902adf33e4ebaa957a8fd0005875029", + "branch": "claude/1232-zero-match" +} From dc718b54556e29e13ad223c06101800e7f90f780 Mon Sep 17 00:00:00 2001 From: thekevinbot <248217931+thekevinbot@users.noreply.github.com> Date: Tue, 6 Oct 2026 13:01:58 -0400 Subject: [PATCH 7/7] e2e attestation for claude/1232-zero-match --- packages/ts/e2e-attestations/claude-1232-zero-match.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ts/e2e-attestations/claude-1232-zero-match.json b/packages/ts/e2e-attestations/claude-1232-zero-match.json index 22823e3cc..b9e89b644 100644 --- a/packages/ts/e2e-attestations/claude-1232-zero-match.json +++ b/packages/ts/e2e-attestations/claude-1232-zero-match.json @@ -1,7 +1,7 @@ { "command": "pnpm test:e2e", - "ran_at": 1791255624, + "ran_at": 1791306118, "exit_code": 0, - "commit": "06afdf1d90d699440246f75c817a00399940dc25", + "commit": "055602415dffd51c3aa8145b39f5d9b0e8988590", "branch": "claude/1232-zero-match" }