query_graph: inbound anti-join unsupported — the README's own dead-code example is rejected, and the OPTIONAL MATCH workaround silently returns 0 rows
Summary
There is no way to express "nodes with no incoming edge" in query_graph. The form the README documents is rejected, and the natural workaround returns zero rows without an error.
The documented example does not work
README, Supported Cypher section:
EXISTS { (n)-[:TYPE]->() }(single-hop existence — great for dead-code, e.g.WHERE NOT EXISTS { (f)<-[:CALLS]-() })
That exact expression is rejected:
WHERE NOT EXISTS { MATCH (f)<-[:CALLS]-() }
-> unsupported EXISTS pattern — only the single-hop form '(var)-[:TYPE]->()' is supportedAll three inbound syntaxes rejected
| Query fragment | Error |
|---|---|
WHERE NOT (f)<-[:CALLS]-() |
unexpected operator at pos 31 |
WHERE NOT EXISTS((f)<-[:CALLS]-()) |
expected '{' after EXISTS |
WHERE NOT EXISTS { MATCH (f)<-[:CALLS]-() } |
unsupported EXISTS pattern … |
The outbound direction works as documented: WHERE NOT EXISTS { (f)-[:CALLS]->() } -> 2,949 leaf functions.
Silent-failure workaround
MATCH (f:Function)
OPTIONAL MATCH (f)<-[:CALLS]-(c)
WITH f, count(c) AS fan_in
WHERE fan_in = 0
RETURN f.name
-> 0 rows, no errorExpected: the ~291 zero-fan-in functions that search_graph(max_degree=0, exclude_entry_points=true) does return.
Impact
Dead-code detection is a headline feature. It is reachable via search_graph(max_degree=0) but not via Cypher, while the README advertises the Cypher form. Three possible fixes, in descending preference: support the inbound single-hop form; or make the OPTIONAL MATCH + WITH count() = 0 path work; or at minimum make that path fail loudly and correct the README example.
Environment
- Version: codebase-memory-mcp 0.9.0 (release binary, darwin-arm64)
- OS: macOS 26.5.2, Apple Silicon
- Repo under test: DeusData/codebase-memory-mcp @ af3ffbd2 (21,143 nodes / 123,103 edges)
- Verified identically through both the MCP server and
climode (outputs byte-identical)
Source: DeusData/codebase-memory-mcp