How to Troubleshoot Graph Build Failures or Missing Nodes in Code-Review-Graph
Graph build failures and missing nodes in code-review-graph typically stem from parser errors, unnormalized Windows paths, outdated SQLite schemas, unresolved bare edges, or stale NetworkX caches, each identifiable through specific CLI commands or API calls.
The code-review-graph (CRG) tool constructs a SQLite-backed knowledge graph from source code using tree-sitter parsers, but builds can fail silently or omit expected nodes when path normalization, schema migrations, or edge resolution steps encounter errors. Understanding how to troubleshoot graph build failures or missing nodes in code-review-graph requires inspecting the parser output, database schema version, and in-memory cache state to isolate whether the issue originates during parsing, storage, or retrieval.
Verify the Parser and Language Support
The first failure point occurs when tree-sitter cannot parse a source file or a language plugin is missing. CRG extracts NodeInfo and EdgeInfo objects through the parser defined in code_review_graph/parser.py.
Run the parser directly on a problematic file to expose language registration errors:
crg parse src/example.py --debug
If the output contains "Skipping … unsupported language", install the missing tree-sitter grammar. The language registration block near the top of parser.py raises a RuntimeError when required parsers are absent, halting the build for that file.
Check Path Normalization and Bridging
Path normalization failures cause graph.get_node() to return None even when the file exists. CRG stores all paths as POSIX strings, but Windows-style paths (C:\proj\src\Foo.java) require bridging.
The normalize_file_path() function in code_review_graph/parser.py converts back-slashes to forward-slashes during ingestion. During lookup, _bridge_qualified_name() in code_review_graph/graph.py (lines 58-66) handles the conversion.
Test the bridging mechanism manually:
from code_review_graph.graph import GraphStore
gs = GraphStore("tmp/graph.db")
# Windows paths are bridged internally to POSIX keys
node = gs.get_node("C:\\proj\\src\\Foo.java::Bar")
print(node) # Should return the node if bridging succeeds
If bridging fails, inspect the normalize_file_path implementation in parser.py to ensure it correctly replaces back-slashes with forward-slashes before storage.
Confirm Schema Version and Run Migrations
Outdated SQLite schemas cause SQL errors or missing tables like nodes_fts and metadata. CRG tracks schema versions in the metadata table, read by get_schema_version() in code_review_graph/migrations.py.
Migrations apply automatically in GraphStore.__init__ (lines 98-107), but manual verification helps diagnose stale databases:
gs = GraphStore("tmp/graph.db")
print(gs.get_metadata("schema_version"))
Compare the output against the MIGRATIONS list in migrations.py. If the version is lower than the latest, call run_migrations(gs._conn) manually, or delete the database file to start fresh with the current schema.
Resolve Bare Edge Identifiers
Bare edges occur when CALLS or TESTED_BY edges contain raw identifiers without fully-qualified names (missing :: separators). These edges carry bare_call_target or bare_tested_by_source keys in their extra JSON column.
The resolution logic resides in GraphStore._resolve_bare_endpoints() (lines 665-885) in graph.py. Inspect raw edges to identify unresolved targets:
for edge in gs.iter_edges_by_source("src/example.py::main"):
print(edge.kind, edge.source_qualified, edge.target_qualified, edge.extra)
If extra contains "bare_call_target": "helper", run the CLI resolution commands:
crg resolve-bare-calls
crg resolve-bare-tested-by
These commands invoke resolve_bare_edge_targets(), converting bare identifiers into fully-qualified names like module::function.
Invalidate Stale NetworkX Caches
CRG caches the NetworkX graph in _nxg_cache to speed up queries. Write operations like store_file_nodes_edges() call _invalidate_cache() (lines 21-25) to clear this cache, but a crash before completion leaves stale data.
Force a cache refresh:
gs._nxg_cache = None # Clear manually
Alternatively, reinstantiate GraphStore to rebuild the cache from the SQLite database on the next access.
Rebuild Full-Text Search Indexes
Missing FTS5 indexes prevent keyword searches from returning results. The search_nodes() function in code_review_graph/graph.py (lines 103-118) queries the nodes_fts virtual table, falling back to LIKE when the table is absent.
Rebuild the index using:
crg rebuild-fts
This executes SQL that drops and recreates the virtual table:
gs._conn.executescript(
"DROP TABLE IF EXISTS nodes_fts; CREATE VIRTUAL TABLE nodes_fts USING fts5(name, qualified_name, content='nodes');"
)
gs._conn.execute(
"INSERT INTO nodes_fts(rowid, name, qualified_name) SELECT id, name, qualified_name FROM nodes;"
)
gs._conn.commit()
Examine Build Logs and Run Diagnostics
The CLI driver in code_review_graph/cli.py uses the logger defined at the top of graph.py. Increase verbosity to capture ERROR and WARNING entries tagged with GRAPH_BUILD:
crg build --log-level DEBUG
For a comprehensive check, run the diagnostic command:
crg diagnose
This validates:
- Database schema version against
MIGRATIONS - Presence of
nodes_ftstable - Existence of at least one node via
has_nodes() - Absence of unresolved bare edges via
_resolve_bare_endpoints()
Complete Troubleshooting Workflow
Combine these steps in sequence to isolate failures:
# 1. Clean slate
crg clean-db
# 2. Rebuild with debug logging
crg build --all --log-level DEBUG
# 3. Resolve incomplete edges
crg resolve-bare-calls
crg resolve-bare-tested-by
# 4. Rebuild search indexes
crg rebuild-fts
# 5. Verify health
crg diagnose
Programmatically inspect specific files:
from code_review_graph.graph import GraphStore
from code_review_graph.parser import parse_file
gs = GraphStore("tmp/graph.db")
# Parse and store atomically
nodes, edges = parse_file("src/main/java/Foo.java")
gs.store_file_nodes_edges("src/main/java/Foo.java", nodes, edges, fhash="abc123")
# Resolve any remaining bare edges
resolved = gs.resolve_bare_edge_targets()
print(f"Resolved {resolved} CALL edges")
# Verify node exists (handles Windows paths automatically)
node = gs.get_node(r"C:\proj\src\main\java\Foo.java::Bar")
print(node)
Summary
- Parser failures indicate missing tree-sitter language grammars or unsupported file types; debug with
crg parse --debug. - Path mismatches between Windows and POSIX formats cause missing nodes; verify
normalize_file_path()and_bridge_qualified_name()behaviors. - Schema mismatches produce SQL errors; check
get_schema_version()and runcrg diagnoseto validate migrations. - Bare edges leave connections unresolved; use
crg resolve-bare-callsand inspectextrametadata ingraph.py. - Stale caches show outdated graph states; clear
_nxg_cachemanually or reinstantiateGraphStore. - Missing FTS indexes break keyword search; rebuild with
crg rebuild-ftswhennodes_ftsis absent.
Frequently Asked Questions
Why does get_node() return None for files that exist in the repository?
This occurs when path normalization fails to bridge Windows-style paths to POSIX keys. CRG stores paths using forward slashes via normalize_file_path() in parser.py, but lookups from Windows environments may pass back-slashes. The _bridge_qualified_name() method in graph.py (lines 58-66) handles this conversion; if it fails, verify that the stored path keys match the lookup format.
How do I fix unresolved CALLS edges after building the graph?
Unresolved edges contain bare identifiers in their extra JSON field, such as {"bare_call_target": "helper"}. Run crg resolve-bare-calls to invoke GraphStore._resolve_bare_endpoints(), which maps these identifiers to fully-qualified names. Check for remaining bare edges by iterating through iter_edges_by_source() and inspecting the edge.extra dictionary.
What causes the nodes_fts table missing error?
The FTS5 virtual table is created during schema migrations; if the database predates the FTS feature or migrations failed, the table is absent. Run crg rebuild-fts to recreate it, or delete the database file to trigger a fresh migration sequence when you next instantiate GraphStore.
Why are my keyword searches returning empty results despite nodes existing in the database?
Searches rely on the nodes_fts table populated by the migration system. If the table exists but is empty, or if the schema version is outdated, search_nodes() falls back to slow LIKE queries that may miss data. Rebuild the index with crg rebuild-fts and verify the schema version using gs.get_metadata("schema_version") to ensure the FTS infrastructure is current.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →