Problem class: rust-cli-silent-empty-report-unknown-prefix
I could not access the repo at ~/<project> on this host (it does not exist here), so the fix below is reconstructed from the problem's behavioral contract. I wrote it to ~/solution.md. The code is drop-in once you locate the graph module handler with the rg commands included; the verification section gives the exact commands to run in the real workspace.
Key points of the fix:
graph module resolved DuckDB before consulting the filesystem, so both the no-graph path and the empty-stats path collapsed to a zeroed success report. A typo and a real-but-uncovered directory were indistinguishable.classify_empty_module(dir_exists) -> UnknownPrefix | UncoveredDirectory, evaluated from Path::new(prefix).exists() before opening the graph, and applied at both exit points.anyhow::anyhow!("'{prefix}' is not in the graph (no such directory and no matching module prefix)") → exit 1; real uncovered dir → unconditional No files in the graph under '<prefix>' - the directory exists but no warmed graph entry covers it. Run \hilo graph warm`.` → exit 0; covered/empty-after-delete (non-empty stats) → normal report.classify_empty_module_unknown_prefix, classify_empty_module_uncovered_directory, plus an integration test running the built binary in a temp dir.RUSTFLAGS="-D warnings" cargo check locally (would recompile libduckdb-sys ~26 min); use scripts/rust-lint.sh + CI.Full markdown (also saved to ~/solution.md):
hilo graph module <prefix> must not silently succeed on an unknown prefixProblem class: rust-cli-silent-empty-report-unknown-prefix
Repo: ~/<project> (rust workspace, clap CLI + DuckDB graph store)
Crate affected: hilo-graph (binary hilo)
hilo graph module <prefix> had exactly one success shape for "nothing to show": print Files: 0 / Edges: 0 / Tests: 0.0% and exit 0. It could not distinguish (1) a real module directory with no warmed graph coverage from (2) a prefix that was never a directory and is not named in the graph (e.g. no/such/dir).
Two things combined:
Sibling graph related <path> / graph impact <path> already fail with exit 1 ... not in the graph ..., so graph module was inconsistent with its own family.
Fix: classify the positional against the filesystem first (before DuckDB) with a pure helper, and apply it on both the no-graph path and the empty-stats path. The tolerant case — directory deleted after a warm, graph still names files under the prefix — has non-empty stats and never reaches the classifier.
No flag changes; the prefix stays the clap positional:
cd ~/<project>
rg -n "No graph data|Files:|Tests:|module" hilo-graph/src --glob '*.rs'
rg -n "graph module|Module" hilo-graph/src --glob '*.rs'
The code below is drop-in for the file implementing the handler (commonly hilo-graph/src/cli/graph.rs or hilo-graph/src/main.rs). It assumes prefix: &str and stats fields files: u64, edges: u64, tests: f64 (rename to match).
use std::path::Path;
/// The result of asking the filesystem whether an empty `graph module`
/// prefix is a typo or a real-but-uncovered directory.
///
/// Deliberately two-state: the tolerant case (directory deleted after a warm,
/// graph still names files under the prefix) never reaches this function
/// because its stats are non-empty and are reported normally.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum EmptyModule {
/// No such path on disk and nothing in the graph under this prefix.
UnknownPrefix,
/// The path exists on disk but nothing in the graph covers it.
UncoveredDirectory,
}
/// Pure, filesystem-only classification of a `graph module` prefix.
///
/// Called *before* graph-db resolution so a fresh/unwarmed tree can answer a
/// typo in the user's own terms rather than with "No graph data".
fn classify_empty_module(dir_exists: bool) -> EmptyModule {
if dir_exists {
EmptyModule::UncoveredDirectory
} else {
EmptyModule::UnknownPrefix
}
}
fn unknown_prefix_error(prefix: &str) -> anyhow::Error {
anyhow::anyhow!(
"'{prefix}' is not in the graph (no such directory and no matching module prefix)"
)
}
fn uncovered_directory_notice(prefix: &str) -> String {
format!(
"No files in the graph under '{prefix}' - the directory exists but no warmed \
graph entry covers it. Run `hilo graph warm`."
)
}
fn run_graph_module(prefix: &str) -> anyhow::Result<()> {
// 1. Filesystem probe FIRST, before any DuckDB resolution.
let dir_exists = Path::new(prefix).exists();
// 2. No-graph path: a fresh tree answers a typo with the prefix error.
let Some(store) = GraphStore::open_default()? else {
return match classify_empty_module(dir_exists) {
EmptyModule::UnknownPrefix => Err(unknown_prefix_error(prefix)),
EmptyModule::UncoveredDirectory => {
// Unconditional, state-independent line; exit 0.
println!("{}", uncovered_directory_notice(prefix));
Ok(())
}
};
};
// 3. Empty-stats path: same classification, same outcomes.
let stats = store.module_stats(prefix)?;
if stats.files == 0 && stats.edges == 0 && stats.tests == 0.0 {
return match classify_empty_module(dir_exists) {
EmptyModule::UnknownPrefix => Err(unknown_prefix_error(prefix)),
EmptyModule::UncoveredDirectory => {
println!("{}", uncovered_directory_notice(prefix));
Ok(())
}
};
}
// 4. Tolerant case: report normally, exit 0.
print_module_report(prefix, &stats);
Ok(())
}
GraphStore::open_default, store.module_stats, and print_module_report are placeholders for the repo's existing symbols. Keep the notice unconditional (no verbose/TTY/empty-graph gate) and change neither the flag set nor --help.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_empty_module_unknown_prefix() {
assert_eq!(classify_empty_module(false), EmptyModule::UnknownPrefix);
}
#[test]
fn classify_empty_module_uncovered_directory() {
assert_eq!(classify_empty_module(true), EmptyModule::UncoveredDirectory);
}
#[test]
fn messages_are_prefixed_and_non_empty() {
let err = unknown_prefix_error("no/such/dir").to_string();
assert!(err.contains("'no/such/dir' is not in the graph"));
let notice = uncovered_directory_notice("hilo-graph/src");
assert!(notice.contains("No files in the graph under 'hilo-graph/src'"));
assert!(notice.contains("hilo graph warm"));
}
}
Under the package owning the binary (adjust CARGO_BIN_EXE_hilo if needed), e.g. hilo-graph/tests/graph_module.rs:
use std::process::Command;
#[test]
fn graph_module_unknown_prefix_fails_loudly_in_fresh_tree() {
let tmp = tempfile::tempdir().expect("tempdir");
let exe = env!("CARGO_BIN_EXE_hilo");
let out = Command::new(exe)
.current_dir(tmp.path()) // fresh dir: no .vfs anywhere above
.args(["graph", "module", "no/such/dir"])
.output()
.expect("run hilo");
assert_eq!(out.status.code(), Some(1), "must exit 1");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("'no/such/dir' is not in the graph"),
"stderr was: {stderr}"
);
}
#[test]
fn graph_module_real_uncovered_directory_exits_zero_with_notice() {
let tmp = tempfile::tempdir().expect("tempdir");
std::fs::create_dir_all(tmp.path().join("real/dir")).unwrap();
let exe = env!("CARGO_BIN_EXE_hilo");
let out = Command::new(exe)
.current_dir(tmp.path())
.args(["graph", "module", "real/dir"])
.output()
.expect("run hilo");
assert_eq!(out.status.code(), Some(0), "must exit 0");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("No files in the graph under 'real/dir'"),
"stdout was: {stdout}"
);
assert!(stdout.contains("hilo graph warm"));
}
Add tempfile = "3" under [dev-dependencies] if not already present.
From ~/<project>:
# 4.1 unit leg (no DuckDB build)
cargo test -p hilo-graph classify_empty_module -- --nocapture
# 4.2 integration test + build
cargo test -p hilo-graph --test graph_module
# 4.3 manual checks
cargo build -p hilo
BIN=./target/debug/hilo
# (a) typo from repo root -> exit 1, prefix named
$BIN graph module no/such/dir; echo "exit=$?"
# (b) typo from a fresh mktemp dir with no .vfs -> exit 1, prefix named
tmp=$(mktemp -d); ( cd "$tmp" && "$OLDPWD/$BIN" graph module no/such/dir; echo "exit=$?" )
# (c) real covered module -> unchanged success (Files 13 / Edges 79)
$BIN graph module hilo-graph/src; echo "exit=$?"
# (d) --help unchanged
$BIN graph module --help
# 4.5 lint leg
./scripts/rust-lint.sh
Expected: (a)/(b) exit 1 with the prefix error (not "No graph data", not a zeroed report); (c) exits 0 with Files: 13 / Edges: 79 byte-identical to pre-fix; (d) help identical; a real uncovered dir exits 0 with the unconditional notice. The tolerant path (delete a directory that still has graph entries) reports normally because non-empty stats bypass the classifier.
Do not put RUSTFLAGS="-D warnings" cargo check in a local gate brief on this host — it changes the dependency fingerprint and recompiles libduckdb-sys C++ from source (~26 min). Use the repo's ./scripts/rust-lint.sh (clippy -D warnings) plus CI for that leg.
| Situation | Dir exists | Graph has prefix | Result |
|---|---|---|---|
| Typo, no graph (fresh tree) | no | no | exit 1, prefix error |
| Typo, graph present | no | no | exit 1, prefix error |
| Real dir, zero coverage | yes | no | exit 0, unconditional No files in the graph ... Run \hilo graph warm`.` |
| Covered module | any | yes | exit 0, normal Files / Edges / Tests % report (tolerant of deleted dir) |
Caveat on verification: the repo was absent at ~/<project> in this environment, so I could not execute the commands above or confirm exact symbol/file names. The behavioral contract and all required elements (pure helper, both exit paths, tolerant case, unconditional message, unit + integration tests, lint/RUSTFLAGS guidance) are fully specified; run Section 4 in the real workspace to close the loop.
# Evidence - Problem class: rust-cli-silent-empty-report-unknown-prefix - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-18T14:18:05.462Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Symptom: `hilo graph module no/such/dir` printed a success-shaped empty report (Files: 0 / Edges: 0 / Tests: 0.0%) and exited 0, indistinguishable from a real but empty module, while sibling subcommands `graph related <path>` and `graph impact <path>` already failed loudly (exit 1, \"not in the graph\"). Fix that worked: classify the prefix against the filesystem BEFORE the graph-db resolution (clap positional, no flag change) into UnknownPrefix / UncoveredDirectory in a private pure helper so the decision is unit-testable without DuckDB; on UnknownPrefix return anyhow::anyhow!(\"'{prefix}' is not in the graph (no such directory and no matching module prefix)\") at BOTH the no-graph path and the empty-stats path (so a fresh unwarmed tree answers a typo with the prefix error, not \"No graph data\"), and keep the tolerant case (directory deleted after a warm, graph still names files under the prefix) reporting normally. For a real directory with zero graph coverage keep exit 0 but print an UNCONDITIONAL, state-independent line: \"No files in the graph under '<prefix>' - the directory exists but no warmed graph entry covers it. Run `hilo graph warm`.\" Evidence: exit 1 naming the prefix from the repo root and from a fresh mktemp dir with no .vfs; `hilo-graph/src` still exit 0 with Files 13 / Edges 79 byte-identical to pre-fix; `--help` unchanged; new unit tests classify_empty_module_unknown_prefix / classify_empty_module_uncovered_directory plus an integration test running the built binary in a temp dir. Pitfall for the foreman: do NOT put RUSTFLAGS=\"-D warnings\" cargo check in a local gate brief on this host - the flag changes the dependency fingerprint and recompiles libduckdb-sys C++ from source (~26 min); the repo's own scripts/rust-lint.sh (clippy -D warnings) plus CI cover that leg.", "environment": "Rust workspace (cargo, clap CLI + DuckDB graph store), Linux x86_64, repo ~/<project> (hilo)", "language": "rust", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "rust-cli-silent-empty-report-unknown-prefix", "provider": "openrouter", "solved_at": "2026-09-18T14:18:05.462Z", "version": "1.96.0"}I could not access the repo at ~/<project> on this host (it does not exist here), so the fix below is reconstructed from the problem's behavioral contract. I wrote it to ~/solution.md. The code is drop-in once you locate the graph module handler with the rg commands included; the verification section gives the exact commands to run in the real workspace.
Key points of the fix:
graph module resolved DuckDB before consulting the filesystem, so both the no-graph path and the empty-stats path collapsed to a zeroed success report. A typo and a real-but-uncovered directory were indistinguishable.classify_empty_module(dir_exists) -> UnknownPrefix | UncoveredDirectory, evaluated from Path::new(prefix).exists() before opening the graph, and applied at both exit points.anyhow::anyhow!("'{prefix}' is not in the graph (no such directory and no matching module prefix)") → exit 1; real uncovered dir → unconditional No files in the graph under '<prefix>' - the directory exists but no warmed graph entry covers it. Run \hilo graph warm`.` → exit 0; covered/empty-after-delete (non-empty stats) → normal report.classify_empty_module_unknown_prefix, classify_empty_module_uncovered_directory, plus an integration test running the built binary in a temp dir.RUSTFLAGS="-D warnings" cargo check locally (would recompile libduckdb-sys ~26 min); use scripts/rust-lint.sh + CI.Full markdown (also saved to ~/solution.md):
hilo graph module <prefix> must not silently succeed on an unknown prefixProblem class: rust-cli-silent-empty-report-unknown-prefix
Repo: ~/<project> (rust workspace, clap CLI + DuckDB graph store)
Crate affected: hilo-graph (binary hilo)
hilo graph module <prefix> had exactly one success shape for "nothing to show": print Files: 0 / Edges: 0 / Tests: 0.0% and exit 0. It could not distinguish (1) a real module directory with no warmed graph coverage from (2) a prefix that was never a directory and is not named in the graph (e.g. no/such/dir).
Two things combined:
Sibling graph related <path> / graph impact <path> already fail with exit 1 ... not in the graph ..., so graph module was inconsistent with its own family.
Fix: classify the positional against the filesystem first (before DuckDB) with a pure helper, and apply it on both the no-graph path and the empty-stats path. The tolerant case — directory deleted after a warm, graph still names files under the prefix — has non-empty stats and never reaches the classifier.
No flag changes; the prefix stays the clap positional:
cd ~/<project>
rg -n "No graph data|Files:|Tests:|module" hilo-graph/src --glob '*.rs'
rg -n "graph module|Module" hilo-graph/src --glob '*.rs'
The code below is drop-in for the file implementing the handler (commonly hilo-graph/src/cli/graph.rs or hilo-graph/src/main.rs). It assumes prefix: &str and stats fields files: u64, edges: u64, tests: f64 (rename to match).
use std::path::Path;
/// The result of asking the filesystem whether an empty `graph module`
/// prefix is a typo or a real-but-uncovered directory.
///
/// Deliberately two-state: the tolerant case (directory deleted after a warm,
/// graph still names files under the prefix) never reaches this function
/// because its stats are non-empty and are reported normally.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum EmptyModule {
/// No such path on disk and nothing in the graph under this prefix.
UnknownPrefix,
/// The path exists on disk but nothing in the graph covers it.
UncoveredDirectory,
}
/// Pure, filesystem-only classification of a `graph module` prefix.
///
/// Called *before* graph-db resolution so a fresh/unwarmed tree can answer a
/// typo in the user's own terms rather than with "No graph data".
fn classify_empty_module(dir_exists: bool) -> EmptyModule {
if dir_exists {
EmptyModule::UncoveredDirectory
} else {
EmptyModule::UnknownPrefix
}
}
fn unknown_prefix_error(prefix: &str) -> anyhow::Error {
anyhow::anyhow!(
"'{prefix}' is not in the graph (no such directory and no matching module prefix)"
)
}
fn uncovered_directory_notice(prefix: &str) -> String {
format!(
"No files in the graph under '{prefix}' - the directory exists but no warmed \
graph entry covers it. Run `hilo graph warm`."
)
}
fn run_graph_module(prefix: &str) -> anyhow::Result<()> {
// 1. Filesystem probe FIRST, before any DuckDB resolution.
let dir_exists = Path::new(prefix).exists();
// 2. No-graph path: a fresh tree answers a typo with the prefix error.
let Some(store) = GraphStore::open_default()? else {
return match classify_empty_module(dir_exists) {
EmptyModule::UnknownPrefix => Err(unknown_prefix_error(prefix)),
EmptyModule::UncoveredDirectory => {
// Unconditional, state-independent line; exit 0.
println!("{}", uncovered_directory_notice(prefix));
Ok(())
}
};
};
// 3. Empty-stats path: same classification, same outcomes.
let stats = store.module_stats(prefix)?;
if stats.files == 0 && stats.edges == 0 && stats.tests == 0.0 {
return match classify_empty_module(dir_exists) {
EmptyModule::UnknownPrefix => Err(unknown_prefix_error(prefix)),
EmptyModule::UncoveredDirectory => {
println!("{}", uncovered_directory_notice(prefix));
Ok(())
}
};
}
// 4. Tolerant case: report normally, exit 0.
print_module_report(prefix, &stats);
Ok(())
}
GraphStore::open_default, store.module_stats, and print_module_report are placeholders for the repo's existing symbols. Keep the notice unconditional (no verbose/TTY/empty-graph gate) and change neither the flag set nor --help.
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_empty_module_unknown_prefix() {
assert_eq!(classify_empty_module(false), EmptyModule::UnknownPrefix);
}
#[test]
fn classify_empty_module_uncovered_directory() {
assert_eq!(classify_empty_module(true), EmptyModule::UncoveredDirectory);
}
#[test]
fn messages_are_prefixed_and_non_empty() {
let err = unknown_prefix_error("no/such/dir").to_string();
assert!(err.contains("'no/such/dir' is not in the graph"));
let notice = uncovered_directory_notice("hilo-graph/src");
assert!(notice.contains("No files in the graph under 'hilo-graph/src'"));
assert!(notice.contains("hilo graph warm"));
}
}
Under the package owning the binary (adjust CARGO_BIN_EXE_hilo if needed), e.g. hilo-graph/tests/graph_module.rs:
use std::process::Command;
#[test]
fn graph_module_unknown_prefix_fails_loudly_in_fresh_tree() {
let tmp = tempfile::tempdir().expect("tempdir");
let exe = env!("CARGO_BIN_EXE_hilo");
let out = Command::new(exe)
.current_dir(tmp.path()) // fresh dir: no .vfs anywhere above
.args(["graph", "module", "no/such/dir"])
.output()
.expect("run hilo");
assert_eq!(out.status.code(), Some(1), "must exit 1");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("'no/such/dir' is not in the graph"),
"stderr was: {stderr}"
);
}
#[test]
fn graph_module_real_uncovered_directory_exits_zero_with_notice() {
let tmp = tempfile::tempdir().expect("tempdir");
std::fs::create_dir_all(tmp.path().join("real/dir")).unwrap();
let exe = env!("CARGO_BIN_EXE_hilo");
let out = Command::new(exe)
.current_dir(tmp.path())
.args(["graph", "module", "real/dir"])
.output()
.expect("run hilo");
assert_eq!(out.status.code(), Some(0), "must exit 0");
let stdout = String::from_utf8_lossy(&out.stdout);
assert!(
stdout.contains("No files in the graph under 'real/dir'"),
"stdout was: {stdout}"
);
assert!(stdout.contains("hilo graph warm"));
}
Add tempfile = "3" under [dev-dependencies] if not already present.
From ~/<project>:
# 4.1 unit leg (no DuckDB build)
cargo test -p hilo-graph classify_empty_module -- --nocapture
# 4.2 integration test + build
cargo test -p hilo-graph --test graph_module
# 4.3 manual checks
cargo build -p hilo
BIN=./target/debug/hilo
# (a) typo from repo root -> exit 1, prefix named
$BIN graph module no/such/dir; echo "exit=$?"
# (b) typo from a fresh mktemp dir with no .vfs -> exit 1, prefix named
tmp=$(mktemp -d); ( cd "$tmp" && "$OLDPWD/$BIN" graph module no/such/dir; echo "exit=$?" )
# (c) real covered module -> unchanged success (Files 13 / Edges 79)
$BIN graph module hilo-graph/src; echo "exit=$?"
# (d) --help unchanged
$BIN graph module --help
# 4.5 lint leg
./scripts/rust-lint.sh
Expected: (a)/(b) exit 1 with the prefix error (not "No graph data", not a zeroed report); (c) exits 0 with Files: 13 / Edges: 79 byte-identical to pre-fix; (d) help identical; a real uncovered dir exits 0 with the unconditional notice. The tolerant path (delete a directory that still has graph entries) reports normally because non-empty stats bypass the classifier.
Do not put RUSTFLAGS="-D warnings" cargo check in a local gate brief on this host — it changes the dependency fingerprint and recompiles libduckdb-sys C++ from source (~26 min). Use the repo's ./scripts/rust-lint.sh (clippy -D warnings) plus CI for that leg.
| Situation | Dir exists | Graph has prefix | Result |
|---|---|---|---|
| Typo, no graph (fresh tree) | no | no | exit 1, prefix error |
| Typo, graph present | no | no | exit 1, prefix error |
| Real dir, zero coverage | yes | no | exit 0, unconditional No files in the graph ... Run \hilo graph warm`.` |
| Covered module | any | yes | exit 0, normal Files / Edges / Tests % report (tolerant of deleted dir) |
Caveat on verification: the repo was absent at ~/<project> in this environment, so I could not execute the commands above or confirm exact symbol/file names. The behavioral contract and all required elements (pure helper, both exit paths, tolerant case, unconditional message, unit + integration tests, lint/RUSTFLAGS guidance) are fully specified; run Section 4 in the real workspace to close the loop.
# Evidence - Problem class: rust-cli-silent-empty-report-unknown-prefix - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-18T14:18:05.462Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Symptom: `hilo graph module no/such/dir` printed a success-shaped empty report (Files: 0 / Edges: 0 / Tests: 0.0%) and exited 0, indistinguishable from a real but empty module, while sibling subcommands `graph related <path>` and `graph impact <path>` already failed loudly (exit 1, \"not in the graph\"). Fix that worked: classify the prefix against the filesystem BEFORE the graph-db resolution (clap positional, no flag change) into UnknownPrefix / UncoveredDirectory in a private pure helper so the decision is unit-testable without DuckDB; on UnknownPrefix return anyhow::anyhow!(\"'{prefix}' is not in the graph (no such directory and no matching module prefix)\") at BOTH the no-graph path and the empty-stats path (so a fresh unwarmed tree answers a typo with the prefix error, not \"No graph data\"), and keep the tolerant case (directory deleted after a warm, graph still names files under the prefix) reporting normally. For a real directory with zero graph coverage keep exit 0 but print an UNCONDITIONAL, state-independent line: \"No files in the graph under '<prefix>' - the directory exists but no warmed graph entry covers it. Run `hilo graph warm`.\" Evidence: exit 1 naming the prefix from the repo root and from a fresh mktemp dir with no .vfs; `hilo-graph/src` still exit 0 with Files 13 / Edges 79 byte-identical to pre-fix; `--help` unchanged; new unit tests classify_empty_module_unknown_prefix / classify_empty_module_uncovered_directory plus an integration test running the built binary in a temp dir. Pitfall for the foreman: do NOT put RUSTFLAGS=\"-D warnings\" cargo check in a local gate brief on this host - the flag changes the dependency fingerprint and recompiles libduckdb-sys C++ from source (~26 min); the repo's own scripts/rust-lint.sh (clippy -D warnings) plus CI cover that leg.", "environment": "Rust workspace (cargo, clap CLI + DuckDB graph store), Linux x86_64, repo ~/<project> (hilo)", "language": "rust", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "rust-cli-silent-empty-report-unknown-prefix", "provider": "openrouter", "solved_at": "2026-09-18T14:18:05.462Z", "version": "1.96.0"}