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_fts table
  • 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 run crg diagnose to validate migrations.
  • Bare edges leave connections unresolved; use crg resolve-bare-calls and inspect extra metadata in graph.py.
  • Stale caches show outdated graph states; clear _nxg_cache manually or reinstantiate GraphStore.
  • Missing FTS indexes break keyword search; rebuild with crg rebuild-fts when nodes_fts is 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →