Skip to content

fix: prevent fn-impact/query crash when -f/--file is passed#1816

Merged
carlos-alm merged 7 commits into
mainfrom
fix/issue-1726-fnimpact-query-file-crash
Jul 6, 2026
Merged

fix: prevent fn-impact/query crash when -f/--file is passed#1816
carlos-alm merged 7 commits into
mainfrom
fix/issue-1726-fnimpact-query-file-crash

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • codegraph fn-impact <name> -f <file> and codegraph query <name> -f <file> crashed with a fatal napi-rs type-conversion error, because -f (via collectFile) always produces a string[], but the native Rust bindings backing these two commands (find_nodes_with_fan_in, fn_deps in crates/codegraph-core/src/db/repository/graph_read.rs) were typed for a single String.
  • context never hit this because it bypasses NativeRepository entirely for symbol lookup (uses raw better-sqlite3), not because it handled the array correctly — confirmed multi-file -f -f genuinely works end-to-end via context/WASM, so the fix widens the native signatures to Vec<String> (OR-of-LIKE, mirroring buildFileConditionSQL) rather than truncating to one file.
  • The suspected "exit code 0 on fatal error" sub-bug did not reproduce — cli.ts's shared run().catch() already sets exit 1 uniformly; the earlier appearance of exit 0 was a bash pipe artifact.

Closes #1726

Test plan

  • npm test — full suite green (3370 passed, 0 failed)
  • npm run lint / npm run typecheck — clean
  • Native addon rebuilt and codesigned locally; verified via before/after cycle (reverted fix, rebuilt, confirmed new tests reproduce the exact reported crash; restored fix, rebuilt, confirmed pass)
  • Cross-checked native vs CODEGRAPH_ENGINE=wasm vs context on 2-file/3-file scoping — identical results (dual-engine parity)

Filed #1815 for a broader out-of-scope pattern found during investigation: triage, interfaces, implementations share the same array-vs-String crash risk in NativeRepository (worse for triage, which silently swallows the error).

Stacked on #1814 (base branch fix/issue-1724-exports-type-only-consumers) — only the graph_read.rs/repository/analysis/test diff is this issue's change.

BATCH_COMMANDS entries with sig: 'dbOnly' (currently only complexity)
always wrote their target into opts.target. complexityData treats
opts.target as a symbol-name filter and opts.file as the file-path
filter, so batch complexity <file> never matched anything and the
function list silently fell back to empty with a whole-repo summary
regardless of which file was requested.

Add an optional targetKey on BatchCommandEntry so each dbOnly command
can declare which opts key its targets map to, and set it to 'file'
for complexity. batchData and multiBatchData now route the target
through the declared key instead of assuming opts.target.

Fixes #1721

Impact: 4 functions changed, 6 affected
…ssification

codegraph roles --role dead flagged every function parameter as dead-leaf and
every interface/type member as dead-unresolved, regardless of actual usage.

Root cause: the role classifier's fan-in-based "no callers = dead" heuristic
is meaningless for these two symbol categories, since neither can ever have
inbound call edges by construction:

- Parameters: `kind = 'parameter'` nodes were unconditionally force-assigned
  dead-leaf via a fast-path bypass in classifyNodeRolesFull/Incremental (TS)
  and do_classify_full/do_classify_incremental (native), before any fan-in
  was even computed. A parameter's liveness is a local dataflow question (is
  it referenced within its own function body), not a call-graph reachability
  question, so this produced ~90% noise in --role dead output.

- Interface/type members: every language extractor qualifies interface/type
  members as `Owner.member` top-level definitions (mirroring class method
  qualification), and they never receive inbound call edges (they're
  consumed via type annotations, not calls). Lacking any recognition of
  this, they fell through the normal fan-in/fan-out path and landed in
  dead-unresolved.

Fix:
- Parameters are now fully excluded from role classification (role stays
  NULL), the same treatment already given to file/directory nodes.
- Interface/type members are now recognized in the classifier by resolving
  the Owner.member prefix against same-file TYPE_DEF_KINDS declarations
  (interface/type/struct/enum/trait/record) and classified `leaf`
  unconditionally. Class methods use the identical Owner.member naming
  convention but are unaffected since `class` is not in TYPE_DEF_KINDS, so
  real dead methods/functions remain detected.

Applied to both the TS/WASM classifier (graph/classifiers/roles.ts,
features/structure.ts) and the native Rust classifier
(graph/classifiers/roles.rs) per the dual-engine parity requirement; verified
both engines produce identical results end-to-end against this repo's own
graph.

Fixes #1723

Impact: 6 functions changed, 5 affected
codegraph exports <file> --json (and the --unused dead-export filter it
feeds) reported zero consumers for interfaces/types that are demonstrably
imported and used elsewhere via `import type { X }`, even though
`codegraph deps <file>` correctly showed the importing file in
`importedBy`. Example: ChaContext in src/domain/graph/builder/cha.ts is
imported (type-only) by build-edges.ts and native-orchestrator.ts, but
`codegraph exports` showed consumerCount: 0.

Root cause: exportsFileImpl's per-symbol consumers query
(domain/analysis/exports.ts) only looked at kind = 'calls' edges. The
builder already emits a symbol-level `imports-type` edge for `import
type { X }` statements (source = importing file node, target = the
specific imported symbol -- see emitTypeOnlySymbolEdges in
build-edges.ts/incremental.ts), which `codegraph deps` reads from, but
the exports consumer query never looked at this edge kind. Role
classification (features/structure.ts) already includes 'imports-type'
in its fan-in formula, so `codegraph roles --role dead` was unaffected --
this was purely a gap in exports's independent consumer list.

Fix: widen the consumers query to `kind IN ('calls', 'imports-type')`,
matching the edge-kind set already used everywhere else in the codebase
for cross-file usage credit (structure.ts, graph-enrichment.ts,
boundaries.ts, dependencies.ts). No native Rust changes needed --
domain/analysis/exports.ts is pure query-layer code that reads the
already-built edges table and has no engine-specific mirror.

Deliberately does NOT add `extends`/`implements`: investigation found
those edges are resolved by symbol name only, with no file/import
scoping (buildClassHierarchyEdges in both build-edges.ts/incremental.ts
and the native emit_hierarchy_edges), so they link same-named
declarations across unrelated files (verified: this repo's own graph has
false `implements`/`extends` edges between unrelated fixture classes
across languages and a `Repository` interface in src/types.ts). Filed
as #1812. Also filed #1813 for a related but distinct gap: `import {
type X }` inline per-specifier modifiers aren't tracked as type-only in
either engine's extractor, so such X still gets no credit even after
this fix.

Added tests/integration/exports.test.ts coverage: an interface consumed
only via a symbol-level `imports-type` edge gets consumerCount >= 1 and
is excluded from --unused, while a genuinely unreferenced interface
still shows 0 consumers.

Verified against this repo's own graph: `codegraph exports
src/domain/graph/builder/cha.ts -T --json` now credits ChaContext with
2 consumers (build-edges.ts, native-orchestrator.ts). Full test suite
(201 files, 3359 tests) and lint pass clean.

docs check acknowledged: internal bug fix to exports consumer
computation, no new feature/language/CLI surface/architecture change --
README.md, CLAUDE.md, and ROADMAP.md are unaffected.

Fixes #1724

Impact: 1 functions changed, 3 affected
The CLI's `-f/--file` option is a repeatable Commander accumulator
(collectFile) that always produces a string[], even on first use. The
native composite bindings backing fn-impact (findNodesWithFanIn) and
query (fnDeps) forwarded that array straight into napi-rs functions
whose Rust signatures only accepted a single String, so any use of
-f/--file crashed with "Failed to convert JavaScript value ... into
rust type `String`" regardless of engine defaults. context worked
only because its code path never touches the native repository for
symbol lookup.

Widen the native Rust signatures (find_nodes_with_fan_in, fn_deps) to
accept Vec<String> and build an OR-of-LIKE clause for multiple files,
mirroring buildFileConditionSQL/NodeQuery.fileFilter on the JS side.
Normalize the file option before calling into the native binding on
the TS side, and thread the widened string | string[] type through
QueryOpts, fnDeps's opts, and the call chain down to
findMatchingNodes. This also makes repeated -f/--file genuinely
multi-file end to end for fn-impact/query, matching context's
existing behavior, instead of crashing on any use.

findNodesByScope shares QueryOpts but has no CLI caller and its
native binding still only accepts one file; it now takes the first
value defensively rather than crash or fail to type-check.

Fixes #1726

docs check acknowledged: pure bug fix, no new CLI options/languages/
architecture — the -f/--file "repeatable" docs are now accurate
rather than needing correction.

Impact: 14 functions changed, 34 affected
@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a fatal napi-rs type-conversion crash that occurred when -f/--file was passed to fn-impact or query: Commander's collectFile accumulator always produces a string[], but the native Rust bindings for find_nodes_with_fan_in and fn_deps previously expected a single String. The fix widens both Rust bindings to Vec<String> and adds a push_file_filter helper that mirrors the existing JS buildFileConditionSQL, ensuring OR-of-LIKE semantics for multi-value scoping.

  • Rust (graph_read.rs): New push_file_filter helper; find_nodes_with_fan_in and fn_deps signatures widened to Option<Vec<String>>; SQL placeholder indexing is 1-based and correct across all call sites.
  • TypeScript: file?: stringfile?: string | string[] propagated through QueryOpts, NativeDatabase, fnDepsData, fnImpactData, findMatchingNodes, and the impact CLI opts; normalizeFileFilter centralises the coercion before every native call; findNodesByScope retains the single-file limitation with an explicit runtime warn() and TODO(#1815).
  • Tests: Regression tests added for single -f, long-form --file, exclusion-of-non-matching-files, and repeated multi-file -f -f for both query and fn-impact, symmetrically.

Confidence Score: 5/5

Safe to merge — the fix is narrowly scoped to widening two Rust napi bindings and normalising the file-filter before each native call, with no changes to query logic or data shapes.

The Rust push_file_filter helper correctly maintains 1-based ?NNN placeholder indexing across all call sites, normalizeFileFilter coercion is already tested elsewhere, and new regression tests cover single-flag, long-form, exclusion, and multi-file paths for both affected commands.

No files require special attention.

Important Files Changed

Filename Overview
crates/codegraph-core/src/db/repository/graph_read.rs Adds push_file_filter helper and widens find_nodes_with_fan_in/fn_deps napi signatures to Option<Vec>; SQL placeholder indexing (1-based ?NNN) is correct at all call sites
src/db/repository/native-repository.ts Uses normalizeFileFilter before each native call; findNodesWithFanIn and fnDeps now pass string[]; findNodesByScope retains single-file workaround with visible warn() and TODO(#1815)
src/types.ts QueryOpts.file and NativeDatabase interface updated to string
tests/integration/cli.test.ts Adds 4 query and 4 fn-impact regression tests covering short-flag, long-flag, exclusion, and multi-file scoping; symmetric coverage for both commands
tests/integration/queries.test.ts Adds unit-level regression tests for fnDepsData and fnImpactData with array-shaped file filters (single-element and multi-element)

Reviews (3): Last reviewed commit: "fix: surface multi-file truncation warni..." | Re-trigger Greptile

Comment thread src/db/repository/native-repository.ts
Comment thread tests/integration/cli.test.ts
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

14 functions changed34 callers affected across 24 files

  • Repository in src/db/repository/base.ts:28 (0 transitive callers)
  • Repository.fnDeps in src/db/repository/base.ts:232 (0 transitive callers)
  • NativeRepository in src/db/repository/native-repository.ts:224 (6 transitive callers)
  • NativeRepository.findNodesWithFanIn in src/db/repository/native-repository.ts:270 (0 transitive callers)
  • NativeRepository.findNodesByScope in src/db/repository/native-repository.ts:305 (0 transitive callers)
  • NativeRepository.fnDeps in src/db/repository/native-repository.ts:531 (0 transitive callers)
  • fnDepsData in src/domain/analysis/dependencies.ts:172 (2 transitive callers)
  • fnImpactData in src/domain/analysis/fn-impact.ts:265 (2 transitive callers)
  • findMatchingNodes in src/domain/analysis/symbol-lookup.ts:38 (29 transitive callers)
  • OutputOpts.file in src/presentation/queries-cli/impact.ts:121 (0 transitive callers)
  • QueryOpts.file in src/types.ts:269 (0 transitive callers)
  • Repository.fnDeps in src/types.ts:348 (0 transitive callers)
  • NativeDatabase.findNodesWithFanIn in src/types.ts:2497 (0 transitive callers)
  • NativeDatabase.fnDeps in src/types.ts:2553 (0 transitive callers)

@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

@carlos-alm carlos-alm deleted the branch main July 6, 2026 01:00
@carlos-alm carlos-alm closed this Jul 6, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 6, 2026
@carlos-alm carlos-alm reopened this Jul 6, 2026
@carlos-alm carlos-alm changed the base branch from fix/issue-1724-exports-type-only-consumers to main July 6, 2026 01:01
@carlos-alm carlos-alm merged commit b97ba1c into main Jul 6, 2026
29 checks passed
@carlos-alm carlos-alm deleted the fix/issue-1726-fnimpact-query-file-crash branch July 6, 2026 01:34
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

codegraph fn-impact and query crash with fatal native error when -f/--file is passed

1 participant