Repo: gethilo/hilo · Fix commit: 661878d5b829c5d1f33693e9771270c10599ab41
I cloned gethilo/hilo, reproduced the bug on the parent commit dd14221, applied/verified the fix at 661878d, and wrote the full write-up to ~/SOLUTION.md. Here it is:
vfs_graph_related Returns Silent [] for Truly Unknown Paths (GAP-062)Repo: gethilo/hilo · Fix commit: 661878d5b829c5d1f33693e9771270c10599ab41
Files: hilo-mcp/src/tools/mod.rs, hilo-mcp/tests/mcp_test.rs
vfs_graph_related answered a non-existent source path with a successful JSON-RPC result containing []:
{"jsonrpc":"2.0","id":41,"result":{"content":[{"type":"text","text":"[]"}]}}
vfs_graph_impact and CLI hilo graph related correctly failed loudly (-32603, '<path>' is not in the graph ...). Since [] is a valid answer for a real node with no edges, unknown and known-empty were indistinguishable.
The MCP handler re-implemented candidate resolution and then fell back to ensure_parsed + related. GraphDB::ensure_parsed is deliberately lenient — for a missing file it returns Ok(Vec::new()) and never errors — so the fallback produced [] for a path absent from both graph and disk.
Meanwhile GraphDB::related_or_parse already encoded the correct contract used by the CLI and impact: node-existence check → GraphError::Other("'<path>' is not in the graph ...") → JSON-RPC -32603.
hilo-mcp/src/tools/mod.rs — track whether any candidate resolved; if none did, delegate to the shared JIT contract:
let mut resolved = false;
let mut edges = Vec::new();
for candidate in &candidates {
if db.file_in_graph(candidate)? {
resolved = true;
edges = db.related(candidate, relation, dir)?;
if !edges.is_empty() {
break;
}
}
}
// No candidate is cached: fall through to the shared JIT contract
// (`GraphDB::related_or_parse`). It parses an on-disk file on the fly and,
// for a path that is neither cached nor on disk, returns the loud
// "is not in the graph" GraphError instead of an empty list.
if !resolved {
edges = db.related_or_parse(target, relation, dir)?;
}
This keeps normalization (/, ./, /home/) for cached nodes, preserves valid [] for real in-graph nodes, JIT-parses real on-disk files, and maps truly unknown paths through the existing -32603 path.
Regression tests added in hilo-mcp/tests/mcp_test.rs (isolated-CWD fixture):
test_graph_related_unknown_path_is_loud_error, test_graph_related_resolves_nodes_and_keeps_valid_empty_results, test_graph_related_jit_parses_uncached_source_file.
Bug reproduced on pre-fix code (reverted only tools/mod.rs to parent dd14221, kept new tests):
$ cargo test -p hilo_mcp test_graph_related -- --test-threads=1
test test_graph_related_unknown_path_is_loud_error ... FAILED
panicked at hilo-mcp/tests/mcp_test.rs:873:9:
absent path no/such/file_gap062.rs must not produce a result:
{"id":41,"jsonrpc":"2.0","result":{"content":[{"text":"[]","type":"text"}]}}
test result: FAILED. 2 passed; 1 failed
Fixed code passes:
$ cargo test -p hilo_mcp test_graph_related -- --test-threads=1
test result: ok. 3 passed; 0 failed
Full gate (all clean, exit 0):
$ cargo fmt --all --check # no output
$ cargo check -p hilo_mcp # Finished, exit 0
$ cargo test -p hilo_mcp # 7 unit + 28 integration, 0 failed
$ cargo clippy -p hilo_mcp -- -D warnings # Finished, exit 0
$ git diff --check # no output
-32603 "is not in the graph"[]graph related and vfs_graph_impactTwo edits to tools/mod.rs: introduce let mut resolved = false;, set resolved = true; when db.file_in_graph(candidate)? matches (dropping the candidate != target || edges.is_empty() guard), and replace the ensure_parsed + related fallback with if !resolved { edges = db.related_or_parse(target, relation, dir)?; }.
The complete write-up (including the full diff and apply commands) is at ~/SOLUTION.md.
# Evidence - Problem class: rust-mcp-related-unknown-path-contract - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-12T20:55:42.657Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Symptom: the MCP vfs_graph_related tool returned a successful empty array for a source path absent from both the graph and disk, while vfs_graph_impact and the CLI graph related command failed loudly. Root cause: the MCP handler reimplemented candidate resolution and then called ensure_parsed plus related; ensure_parsed is a no-op for missing paths, so related produced an empty result. Fix: retain exact and normalized-prefix candidate resolution for cached graph nodes, track whether any candidate resolved, and on total cache miss delegate to GraphDB::related_or_parse. This preserves valid empty results for real graph nodes, JIT-parses real uncached files, and maps truly unknown paths through the existing JSON-RPC -32603 error path. Verification: cargo fmt --all --check, cargo check -p hilo_mcp, cargo test -p hilo_mcp (28 integration tests plus 7 unit tests, zero failures), cargo clippy -p hilo_mcp -- -D warnings, git diff --check, GitReins Tier 1 PASS, and GitReins Tier 2 PASS verdict 371f1da7.", "environment": "Linux; Rust workspace; Hilo MCP stdio JSON-RPC surface", "language": "rust", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "rust-mcp-related-unknown-path-contract", "provider": "openrouter", "solved_at": "2026-09-12T20:55:42.657Z", "version": ""}I cloned gethilo/hilo, reproduced the bug on the parent commit dd14221, applied/verified the fix at 661878d, and wrote the full write-up to ~/SOLUTION.md. Here it is:
vfs_graph_related Returns Silent [] for Truly Unknown Paths (GAP-062)Repo: gethilo/hilo · Fix commit: 661878d5b829c5d1f33693e9771270c10599ab41
Files: hilo-mcp/src/tools/mod.rs, hilo-mcp/tests/mcp_test.rs
vfs_graph_related answered a non-existent source path with a successful JSON-RPC result containing []:
{"jsonrpc":"2.0","id":41,"result":{"content":[{"type":"text","text":"[]"}]}}
vfs_graph_impact and CLI hilo graph related correctly failed loudly (-32603, '<path>' is not in the graph ...). Since [] is a valid answer for a real node with no edges, unknown and known-empty were indistinguishable.
The MCP handler re-implemented candidate resolution and then fell back to ensure_parsed + related. GraphDB::ensure_parsed is deliberately lenient — for a missing file it returns Ok(Vec::new()) and never errors — so the fallback produced [] for a path absent from both graph and disk.
Meanwhile GraphDB::related_or_parse already encoded the correct contract used by the CLI and impact: node-existence check → GraphError::Other("'<path>' is not in the graph ...") → JSON-RPC -32603.
hilo-mcp/src/tools/mod.rs — track whether any candidate resolved; if none did, delegate to the shared JIT contract:
let mut resolved = false;
let mut edges = Vec::new();
for candidate in &candidates {
if db.file_in_graph(candidate)? {
resolved = true;
edges = db.related(candidate, relation, dir)?;
if !edges.is_empty() {
break;
}
}
}
// No candidate is cached: fall through to the shared JIT contract
// (`GraphDB::related_or_parse`). It parses an on-disk file on the fly and,
// for a path that is neither cached nor on disk, returns the loud
// "is not in the graph" GraphError instead of an empty list.
if !resolved {
edges = db.related_or_parse(target, relation, dir)?;
}
This keeps normalization (/, ./, /home/) for cached nodes, preserves valid [] for real in-graph nodes, JIT-parses real on-disk files, and maps truly unknown paths through the existing -32603 path.
Regression tests added in hilo-mcp/tests/mcp_test.rs (isolated-CWD fixture):
test_graph_related_unknown_path_is_loud_error, test_graph_related_resolves_nodes_and_keeps_valid_empty_results, test_graph_related_jit_parses_uncached_source_file.
Bug reproduced on pre-fix code (reverted only tools/mod.rs to parent dd14221, kept new tests):
$ cargo test -p hilo_mcp test_graph_related -- --test-threads=1
test test_graph_related_unknown_path_is_loud_error ... FAILED
panicked at hilo-mcp/tests/mcp_test.rs:873:9:
absent path no/such/file_gap062.rs must not produce a result:
{"id":41,"jsonrpc":"2.0","result":{"content":[{"text":"[]","type":"text"}]}}
test result: FAILED. 2 passed; 1 failed
Fixed code passes:
$ cargo test -p hilo_mcp test_graph_related -- --test-threads=1
test result: ok. 3 passed; 0 failed
Full gate (all clean, exit 0):
$ cargo fmt --all --check # no output
$ cargo check -p hilo_mcp # Finished, exit 0
$ cargo test -p hilo_mcp # 7 unit + 28 integration, 0 failed
$ cargo clippy -p hilo_mcp -- -D warnings # Finished, exit 0
$ git diff --check # no output
-32603 "is not in the graph"[]graph related and vfs_graph_impactTwo edits to tools/mod.rs: introduce let mut resolved = false;, set resolved = true; when db.file_in_graph(candidate)? matches (dropping the candidate != target || edges.is_empty() guard), and replace the ensure_parsed + related fallback with if !resolved { edges = db.related_or_parse(target, relation, dir)?; }.
The complete write-up (including the full diff and apply commands) is at ~/SOLUTION.md.
# Evidence - Problem class: rust-mcp-related-unknown-path-contract - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-12T20:55:42.657Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Symptom: the MCP vfs_graph_related tool returned a successful empty array for a source path absent from both the graph and disk, while vfs_graph_impact and the CLI graph related command failed loudly. Root cause: the MCP handler reimplemented candidate resolution and then called ensure_parsed plus related; ensure_parsed is a no-op for missing paths, so related produced an empty result. Fix: retain exact and normalized-prefix candidate resolution for cached graph nodes, track whether any candidate resolved, and on total cache miss delegate to GraphDB::related_or_parse. This preserves valid empty results for real graph nodes, JIT-parses real uncached files, and maps truly unknown paths through the existing JSON-RPC -32603 error path. Verification: cargo fmt --all --check, cargo check -p hilo_mcp, cargo test -p hilo_mcp (28 integration tests plus 7 unit tests, zero failures), cargo clippy -p hilo_mcp -- -D warnings, git diff --check, GitReins Tier 1 PASS, and GitReins Tier 2 PASS verdict 371f1da7.", "environment": "Linux; Rust workspace; Hilo MCP stdio JSON-RPC surface", "language": "rust", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "rust-mcp-related-unknown-path-contract", "provider": "openrouter", "solved_at": "2026-09-12T20:55:42.657Z", "version": ""}