Two independent v0.10.0 bugs that share a failure shape: both return an empty result with a success status instead of either data or an error. Filing together because that shape is the actionable part — for an LLM client, "empty" and "nothing found" are indistinguishable, so both produce silent false conclusions rather than visible failures. Happy to split into two issues if you'd prefer.
Environment
codebase-memory-mcp 0.10.0, windows-amd64 release zip
- Windows 11 (26100), installed to a custom
--dir
- MCP client: Claude Code (stdio)
- Project indexed with
--mode full, status: ready, 4770 nodes / 21837 edges, parse_partial: 0, skipped: 0
Bug 1 — format: "tree" returns bare {} over MCP (default format, so every naive call is affected)
search_graph and trace_path return an empty JSON object {} over the MCP transport whenever the output format is tree. Since tree is the default, an ordinary call with no format argument silently yields nothing. The identical query with format: "json" returns full, correct results, so the query engine and the index are fine — it looks isolated to the MCP-side tree encoder.
Critically, the same tree format renders correctly over the CLI, which is what localizes this to the MCP response path rather than the formatter itself.
Reproduction
Same binary, same index, same project, four consecutive calls:
| # |
Call |
Result |
| 1 |
search_graph(project=P, name_pattern="_build_and_push_graph") — no format |
{} |
| 2 |
search_graph(project=P, name_pattern="_build_and_push_graph", format="tree") |
{} |
| 3 |
search_graph(project=P, name_pattern="_build_and_push_graph", format="json") |
correct, total: 2 |
| 4 |
trace_path(project=P, function_name="similarity_edge_key", mode="calls", direction="inbound") — no format |
{} |
| 5 |
same as 4 with format="json" |
correct, callers_total: 3 |
Call 3 output (abridged):
{"total":2,"count":2,"cols":["name","label","lines","in","out"],
"groups":[{"qn_prefix":"<project>.<pkg>.main","file":"<pkg>/main.py",
"rows":[["_build_and_push_graph","Function","1457-1936",6,66]]}],
"has_more":false}
Call 5 output:
{"function":"similarity_edge_key","direction":"inbound","mode":"calls",
"callers_total":3,
"callers":{"cols":["name","hop"],"groups":[
{"qn_prefix":"<project>.<pkg>.graph.builder","rows":[["to_graphology_json",1]]},
{"qn_prefix":"<project>.<pkg>.main","rows":[["_build_and_push_graph",2]]}]}}
CLI contrast — tree works here
$ codebase-memory-mcp cli search_graph --project <P> --name-pattern "_build_and_push_graph"
total: 2
results: 2 (rows: name label lines in out; qn = group prefix + "." + name)
<project>.<pkg>.main (<pkg>/main.py):
_build_and_push_graph Function 1457-1936 6 66
<project>.<pkg>.tests.test_exceptions (<pkg>/tests/test_exceptions.py):
test_r3_cache_key_construction_matches_build_and_push_graph_source Function 356-370 0 1
has_more: false
Explicit --format tree over the CLI is identical. So: CLI tree ✅, MCP tree ❌, MCP json ✅.
Why this one is worse than it looks
{} is indistinguishable from a genuine zero-result search. An agent following the server's own instructions ("use graph tools first for structural code discovery") will conclude a symbol has no callers, or does not exist, and fall back to grep or to an assumption — with no error anywhere to signal that the answer was wrong. The failure is silent, and it hits the default code path.
Suggested fix, in preference order: fix the MCP tree encoder; failing that, return an explicit error rather than {} when encoding yields nothing, so callers can distinguish "no matches" from "could not encode".
Bug 2 — config get returns empty with exit 0 for every key except auto_index
config get <key> prints a value only for auto_index. Every other valid key returns an empty string and exit code 0 — as does a deliberately invalid key, so a typo is indistinguishable from a correctly-read setting.
Reproduction
$ for k in auto_index auto_watch auto_index_limit ui-lang totally_bogus_key; do
printf '%-20s -> [%s] exit=%s\n' "$k" "$(codebase-memory-mcp config get $k)" "$?"
done
auto_index -> [true] exit=0
auto_watch -> [] exit=0
auto_index_limit -> [] exit=0
ui-lang -> [] exit=0
totally_bogus_key -> [] exit=0
config list reports all four correctly in the same session, so the values are stored and readable — it is the get path that drops them:
Configuration:
auto_index = true
auto_index_limit = 50000
auto_watch = true
ui-lang = auto
Expected: config get auto_watch prints true; an unknown key exits non-zero with an error naming the key.
Note on how these were found
Surfaced while verifying an 0.8.1 → 0.10.0 upgrade. Unrelated to both, but worth flagging in case others hit it during the same upgrade: every .db in the data dir had been left with an empty DACL on Windows (D:AI, zero ACEs), which made the daemon fail at startup with
level=error msg=daemon.runtime_config_open_failed reason=config_db_unavailable
level=error msg=daemon.start_failed component=application
while every client reported the misleading CBM daemon is active or starting but could not accept this client within 30000 ms — because the dead daemon left its lock files behind. I could not determine what stripped the ACLs and cannot attribute it to this project; files recreated by 0.10.0 get correct ACEs (SYSTEM full, logon-session read/execute, owner full), so this is not a reproducible bug report. The only suggestion worth making is that config_db_unavailable would be far easier to diagnose if it distinguished "permission denied" from "locked" and included the path.
Two independent v0.10.0 bugs that share a failure shape: both return an empty result with a success status instead of either data or an error. Filing together because that shape is the actionable part — for an LLM client, "empty" and "nothing found" are indistinguishable, so both produce silent false conclusions rather than visible failures. Happy to split into two issues if you'd prefer.
Environment
codebase-memory-mcp 0.10.0,windows-amd64release zip--dir--mode full,status: ready, 4770 nodes / 21837 edges,parse_partial: 0,skipped: 0Bug 1 —
format: "tree"returns bare{}over MCP (default format, so every naive call is affected)search_graphandtrace_pathreturn an empty JSON object{}over the MCP transport whenever the output format istree. Sincetreeis the default, an ordinary call with noformatargument silently yields nothing. The identical query withformat: "json"returns full, correct results, so the query engine and the index are fine — it looks isolated to the MCP-side tree encoder.Critically, the same tree format renders correctly over the CLI, which is what localizes this to the MCP response path rather than the formatter itself.
Reproduction
Same binary, same index, same project, four consecutive calls:
search_graph(project=P, name_pattern="_build_and_push_graph")— noformat{}search_graph(project=P, name_pattern="_build_and_push_graph", format="tree"){}search_graph(project=P, name_pattern="_build_and_push_graph", format="json")total: 2trace_path(project=P, function_name="similarity_edge_key", mode="calls", direction="inbound")— noformat{}format="json"callers_total: 3Call 3 output (abridged):
{"total":2,"count":2,"cols":["name","label","lines","in","out"], "groups":[{"qn_prefix":"<project>.<pkg>.main","file":"<pkg>/main.py", "rows":[["_build_and_push_graph","Function","1457-1936",6,66]]}], "has_more":false}Call 5 output:
{"function":"similarity_edge_key","direction":"inbound","mode":"calls", "callers_total":3, "callers":{"cols":["name","hop"],"groups":[ {"qn_prefix":"<project>.<pkg>.graph.builder","rows":[["to_graphology_json",1]]}, {"qn_prefix":"<project>.<pkg>.main","rows":[["_build_and_push_graph",2]]}]}}CLI contrast — tree works here
Explicit
--format treeover the CLI is identical. So: CLI tree ✅, MCP tree ❌, MCP json ✅.Why this one is worse than it looks
{}is indistinguishable from a genuine zero-result search. An agent following the server's own instructions ("use graph tools first for structural code discovery") will conclude a symbol has no callers, or does not exist, and fall back to grep or to an assumption — with no error anywhere to signal that the answer was wrong. The failure is silent, and it hits the default code path.Suggested fix, in preference order: fix the MCP tree encoder; failing that, return an explicit error rather than
{}when encoding yields nothing, so callers can distinguish "no matches" from "could not encode".Bug 2 —
config getreturns empty with exit 0 for every key exceptauto_indexconfig get <key>prints a value only forauto_index. Every other valid key returns an empty string and exit code 0 — as does a deliberately invalid key, so a typo is indistinguishable from a correctly-read setting.Reproduction
config listreports all four correctly in the same session, so the values are stored and readable — it is thegetpath that drops them:Expected:
config get auto_watchprintstrue; an unknown key exits non-zero with an error naming the key.Note on how these were found
Surfaced while verifying an 0.8.1 → 0.10.0 upgrade. Unrelated to both, but worth flagging in case others hit it during the same upgrade: every
.dbin the data dir had been left with an empty DACL on Windows (D:AI, zero ACEs), which made the daemon fail at startup withwhile every client reported the misleading
CBM daemon is active or starting but could not accept this client within 30000 ms— because the dead daemon left its lock files behind. I could not determine what stripped the ACLs and cannot attribute it to this project; files recreated by 0.10.0 get correct ACEs (SYSTEMfull, logon-session read/execute, owner full), so this is not a reproducible bug report. The only suggestion worth making is thatconfig_db_unavailablewould be far easier to diagnose if it distinguished "permission denied" from "locked" and included the path.