Skip to content

fix: couple file_hashes updates with edge regeneration in incremental builds#1828

Merged
carlos-alm merged 12 commits into
mainfrom
fix/issue-1731-stale-edges-hash-match
Jul 6, 2026
Merged

fix: couple file_hashes updates with edge regeneration in incremental builds#1828
carlos-alm merged 12 commits into
mainfrom
fix/issue-1731-stale-edges-hash-match

Conversation

@carlos-alm

Copy link
Copy Markdown
Contributor

Summary

  • In all three places that rebuild a file's dependency edges (JS/WASM pipeline, native Rust pipeline, watch mode), the file_hashes write and edge-set replacement happened in separate, independently-committed transactions, with the hash committed first (during node insertion, before resolveImports/buildEdges run).
  • Since change-detection trusts file_hashes exclusively, any exception/interruption between the hash commit and edge rebuild left the DB permanently believing a file was up to date while its edges were stale or missing — no later build would ever revisit it.
  • Fixed by deferring the file_hashes commit until after edges are rebuilt in all three paths (new commitFileHashes()/commit_file_hashes called post-buildEdges; watch-mode rebuildFile now writes/deletes the hash only after its edge rebuild completes). Removed-file hash cleanup stays immediate since it carries no coupling risk.

Closes #1731

Test plan

  • npm test — full suite green (3401 passed, 0 failed)
  • npm run lint / npx tsc --noEmit — clean
  • cargo check --release / cargo test --release — clean / 427 passed
  • Native addon rebuilt, codesigned, verified directly (full build → incremental edit → no-op rebuild, plus a WASM→native engine-switch scenario)
  • New fault-injection test: throws inside buildEdges mid-incremental-build, asserts the hash doesn't advance until edges genuinely match, and that a retry self-heals — reverted the fix locally to confirm this test fails without it

Filed #1827 for an out-of-scope finding: several Rust edge-insertion functions swallow transaction/Result failures instead of propagating them, which is related but requires a broader signature-propagation change.

Stacked on #1826 (base branch fix/issue-1730-exports-renamed-import-consumers) — only the pipeline/insert-nodes/watcher/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
… exports

Two compounding root causes made `where --file`'s `exported` array (and
`codegraph exports`) unreliable for entire classes of exports:

1. The JS/TS export-statement handler (WASM query path, WASM walk path, and
   the mirrored native Rust extractor) only recognized `export function`,
   `export class`, `export interface`, and `export type` declarations. It
   never matched `lexical_declaration`/`variable_declaration`, so
   `export const/let/var …` was never added to the extractor's export list —
   regardless of the initializer's shape — leaving the `exported=1` DB column
   permanently unset for every exported constant in the codebase (including
   ones that happened to look "correct", like a `new Set(...)`-initialized
   constant referenced as a call argument elsewhere).

2. `where --file`'s `exported` list ignored the `exported` DB column entirely
   and computed membership from `findCrossFileCallTargets` (symbols targeted
   by a cross-file `calls` edge) — a heuristic that only "worked" by
   coincidence for symbols that happened to be passed as call arguments
   elsewhere. `codegraph exports` already had the right idea (prefer the
   `exported` column, fall back to the heuristic only for pre-migration DBs)
   but duplicated that logic locally instead of sharing it.

Fixes:
- src/extractors/javascript.ts: extract the function/class/interface/type
  kind map plus a new lexical/variable-declaration branch into a shared
  `collectExportedDeclarations`, used by both `handleExportCapture` (query
  path) and `handleExportStmt` (walk path) so they can't drift apart again.
  Declarator values are classified with the same predicate already used to
  build the matching Definition (function-valued -> kind 'function',
  literal/array/object/new-expression-valued `const` -> kind 'constant').
- crates/codegraph-core/src/extractors/javascript.rs: mirror the same fix in
  `handle_export_declaration` via a new `collect_exported_var_declarations`.
- src/db/repository/nodes.ts: add `findExportedNodesByFile`, the single
  shared implementation of "prefer exported=1, fall back to cross-file calls
  for pre-migration DBs" — re-exported through db/repository/index.ts and
  db/index.ts.
- src/domain/analysis/exports.ts: use the shared helper instead of a locally
  duplicated hasExportedCol/exportedNodesStmt implementation.
- src/domain/analysis/symbol-lookup.ts: `whereFileImpl` now uses the shared
  helper instead of `findCrossFileCallTargets` directly, so `where --file`
  and `exports` agree on what "exported" means.

Tests:
- tests/parsers/javascript.test.ts: unit coverage for bare-literal, new
  Set(...), object-literal-with-methods, and arrow-function exported consts,
  plus a non-exported-const negative case and a function/class regression
  guard for the shared-helper refactor.
- tests/engines/query-walk-parity.test.ts, tests/engines/parity.test.ts:
  cross-path/cross-engine regression cases for the same shapes.
- tests/integration/queries.test.ts: new fixture node with exported=1 but
  zero incoming edges of any kind, proving `where --file`'s exported list
  no longer depends on cross-file call presence.

No user-facing command/language/architecture changes, so README/CLAUDE.md/
ROADMAP.md are unaffected — docs check acknowledged.

Fixes #1728

Impact: 8 functions changed, 17 affected
tree-sitter-typescript's predefined_type production (string, number,
boolean, ... primitive type keywords) lexes its keyword as an anonymous
grammar token whose type string is identical to the named `string` node
type used for real string literals. Both engines matched by node type
alone, so `codegraph ast --kind string` spuriously matched bare `string`
type annotations (interface fields, parameter types, return types) as if
they were string literals.

Add an isNamed/is_named() guard scoped to the JS/TS/TSX family on both
engines: WASM via astRequiresNamedNode() in ast-analysis/rules/index.ts
feeding the shared resolveAstKind() in ast-store-visitor.ts, native via a
match guard in javascript.rs's walk_ast_nodes_depth. Genuine string,
template, and string-literal-type nodes are always named and unaffected.

No user-facing command/language/architecture changes, so README/CLAUDE.md/
ROADMAP.md are unaffected — docs check acknowledged.

Fixes #1729

Impact: 8 functions changed, 13 affected
codegraph exports <file> reported zero consumers for an exported
function/symbol that is genuinely imported and used elsewhere, whenever
the importing file renamed the binding at the import site
(import { X as Y } from '...'). Example: collectFiles in
src/domain/graph/builder/helpers.ts is imported (as collectFilesUtil)
and called by collect-files.ts and native-orchestrator.ts, but
`codegraph exports helpers.ts -T --json` showed consumerCount: 0.

Root cause (extractor, both engines): extractImportNames (javascript.ts)
and its mirrored scan_import_names_depth (javascript.rs) handled
import_specifier nodes with `node.childForFieldName('name') ||
childForFieldName('alias')`. Per the tree-sitter grammar, `name` is
*always* present on import_specifier (the name as declared by the source
module); `alias` only exists for `X as Y` and holds the *local* binding
actually referenced by call sites. Preferring `name` unconditionally
meant the local alias was silently dropped for every renamed import —
`imp.names` recorded "collectFiles" instead of "collectFilesUtil", so
`importedNames` (keyed by call-site text) never had a matching entry and
the call fell through unresolved.

Fixing only the extractor was not sufficient: once `imp.names` correctly
holds the local alias, `resolveCallTargets` (call-resolver.ts) /
resolve_call_targets (build_edges.rs) still searched the *target file*
for a symbol literally named "collectFilesUtil" — which doesn't exist
there (only "collectFiles" does). This required threading a second
piece of information end-to-end: for each renamed specifier, the local
alias's *original* exported name, so target-file/barrel lookups search
by the right name while importedNames stays keyed by the call-site text.

Changes (both engines, full-build + incremental/native-orchestrator +
native-hybrid paths):
- types.ts / types.rs: new `Import.renamedImports` /
  `Import.renamed_imports` field — { local, imported } pairs, populated
  only for specifiers that actually rename a binding.
- extractors/javascript.ts, .rs: extractImportNames /
  extract_import_names_with_renames now prefer `alias` (local binding)
  for import_specifier and record the rename pair. export_specifier is
  deliberately left unchanged (see scope notes below). Fixed in both the
  walk path (handleImportStmt) and the query/WASM-worker path
  (handleImportCapture) on the TS side.
- build-edges.ts, build_edges.rs, import_edges.rs, pipeline.rs,
  call-resolver.ts, incremental.ts: buildImportedNamesMap /
  collect_imported_names_for_file now also produce a local-alias ->
  original-name map; resolveCallTargets / resolve_call_targets consult
  it to search the target file by the original name. Applied
  consistently to the primary call-resolution path, the
  Object.defineProperty post-pass, points-to alias resolution, barrel
  edge / import-type edge emission, and cross-file return-type
  propagation — every place that previously assumed a call site's local
  name equals the target file's declared name.

Native Rust: touched and verified. Rebuilt locally via
`npx napi build --platform --release` (crates/codegraph-core), codesigned
(`codesign --sign - --force`), and installed over the loaded
node_modules/@optave/codegraph-darwin-arm64/codegraph-core.node so the
fix was exercised by the actual native engine, not the prebuilt npm
binary. `cargo test --lib`: 427 passed.

Tests added:
- tests/parsers/javascript.test.ts: extractor-level coverage for the
  local-name/rename-pair extraction, a mixed renamed+non-renamed
  specifier list, and confirmation export_specifier is unaffected.
- crates/codegraph-core/src/extractors/javascript.rs: matching Rust unit
  tests.
- tests/integration/issue-1730-renamed-import-consumer.test.ts: new
  end-to-end test building real files through buildGraph() on both wasm
  and native engines, asserting the calls edge exists, no edge is
  created against a nonexistent "collectFilesUtil" symbol, and
  `codegraph exports` credits the consumer — on both engines.
  tests/integration/exports.test.ts was not extended: that file
  hand-inserts DB rows and only exercises the query layer, which is
  unaffected by this bug (the missing piece was edge creation, not the
  consumer query added for #1724).

Verified against this repo's own graph (native engine, rebuilt):
`codegraph exports src/domain/graph/builder/helpers.ts -T --json` now
credits collectFiles with 2 consumers (collect-files.ts,
native-orchestrator.ts); WASM engine agrees exactly. Without -T, also
picks up tests/unit/builder.test.ts via the (unrenamed) builder.ts
barrel re-export, which already worked pre-fix and was only hidden by
-T in the original repro.

Scope notes — filed as separate issues rather than expanding this fix:
- #1823: export { X as Y } from '...' (barrel re-export with rename) is
  not tracked — resolveBarrelExport/resolve_barrel_export key off the
  original name, and export_specifier extraction was deliberately left
  unchanged here since barrel/reexport tracing is a distinct mechanism.
- #1824: dynamic import destructuring rename
  (const { a: b } = await import(...)) has the same class of bug in
  extractDynamicImportNames, a different extraction function.
- #1825: a renamed import used as a call receiver
  (import { X as Y }; Y.method()) still fails to resolve — confirmed
  empirically (no calls edge created) — a different resolution strategy
  (resolveByReceiver/resolveViaDirectQualifiedMethod in
  resolver/strategy.ts) than the one this fix addresses.

Full test suite (202 files, 3398 tests) and lint pass clean.

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

Fixes #1730

Impact: 29 functions changed, 47 affected
… builds

insertNodes committed file_hashes for changed files in the same
transaction as node insertion, before resolveImports/buildEdges rebuilt
those files' edges (a separate, later stage/transaction) — in both the
JS/WASM pipeline and the native Rust orchestrator. Any exception, crash,
or interruption between the two left a hash that claimed a file was
"up to date" while its edges still reflected an older revision. Since
change-detection trusts file_hashes exclusively, that divergence was
never self-healed by later builds.

Defer the file_hashes commit so it only runs once edges have been
rebuilt to match:
- insert-nodes.ts: insertNodes no longer writes file_hashes for changed
  files; new commitFileHashes() does it, called from pipeline.ts after
  buildEdges.
- insert_nodes.rs / pipeline.rs / connection.rs: do_insert_nodes no
  longer upserts file_hashes (only removed-file cleanup, which has no
  coupling risk); new commit_file_hashes() runs after Stage 7 (edges).

Watch-mode's rebuildFile never wrote file_hashes at all, leaving it
permanently stale after every edit — also fixed by writing/deleting the
hash once a file's edges have been rebuilt or the file is deleted.

Adds tests/integration/issue-1731-hash-edge-coupling.test.ts: a
fault-injection test that throws inside buildEdges mid-incremental-build
and asserts the hash does not advance until edges genuinely match (and
that a retry self-heals), plus coverage for rebuildFile keeping
file_hashes in sync with its edge rebuilds.

Fixes #1731

Impact: 8 functions changed, 13 affected
@greptile-apps

greptile-apps Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a durability bug (#1731) where file_hashes was committed during node insertion (Stage 5), before edge rebuilding (Stage 7), leaving the DB in a permanently corrupt state if anything failed between the two. The fix defers the hash write to a new commitFileHashes/commit_file_hashes step called strictly after edges have been rebuilt, across all three pipelines (JS/WASM, native Rust orchestrator, and watch-mode rebuildFile).

  • JS/WASM pipeline (insert-nodes.ts, pipeline.ts): insertNodes no longer writes changed-file hashes; a new commitFileHashes(ctx) is called from the pipeline after buildEdges completes.
  • Native Rust pipeline (insert_nodes.rs, pipeline.rs): do_insert_nodes drops the file_hashes parameter; a new commit_file_hashes is called in Stage 7 after all edge-building functions complete.
  • Watch mode (incremental.ts, watcher.ts): rebuildFile now writes/deletes the hash only after its edge rebuild completes, using two new upsertFileHash/deleteFileHash statements added to IncrementalStmts; a new fault-injection regression test verifies the invariant end-to-end.

Confidence Score: 5/5

Safe to merge — the fix is surgical, well-documented, and covered by a new fault-injection regression test.

The change correctly couples hash writes to post-edge-build across all three execution paths. Removed-file hash cleanup stays immediate, which is safe since those files carry no edges to keep in sync. The new bulk_insert_nodes short-circuit in Rust correctly prevents hash commits after failed node insertion. Error handling is consistent and the self-healing property is preserved in all failure modes.

No files require special attention.

Important Files Changed

Filename Overview
src/domain/graph/builder/stages/insert-nodes.ts Removes file_hashes writes from insertNodes; adds new commitFileHashes() called post-buildEdges, with correct fallback paths for native/JS engines and graceful error handling.
src/domain/graph/builder/pipeline.ts Adds commitFileHashes(ctx) call after buildEdges and before buildStructure — correctly placed so a failure in structure/analysis still benefits from a consistent hash.
src/domain/graph/builder/incremental.ts Adds upsertFileHash/deleteFileHash to IncrementalStmts; rebuildFile now commits the hash after edges are rebuilt, and removes it immediately on file deletion.
src/domain/graph/watcher.ts Adds upsertFileHash and deleteFileHash prepared statements to prepareWatcherStatements, fulfilling the new IncrementalStmts contract.
crates/codegraph-core/src/domain/graph/builder/stages/insert_nodes.rs Splits do_insert_nodes (now without file_hashes) and adds commit_file_hashes; correctly separates upsert_file_hashes from delete_removed_file_hashes with a shared has_file_hashes_table helper.
crates/codegraph-core/src/domain/graph/builder/pipeline.rs Defers file_hashes from Stage 5 to end of Stage 7 via commit_file_hashes; error is logged but not propagated, consistent with the existing do_insert_nodes call-site behavior.
crates/codegraph-core/src/db/connection.rs bulk_insert_nodes now short-circuits before commit_file_hashes if node insertion fails; previously addressed comment is properly handled.
tests/integration/issue-1731-hash-edge-coupling.test.ts New regression test: Suite 1 fault-injects a buildEdges failure and asserts the hash doesn't advance; Suite 2 tests watch-mode rebuildFile hash sync and deletion.
tests/integration/watcher-rebuild.test.ts Adds upsertFileHash/deleteFileHash to makeStmts fixture to satisfy the updated IncrementalStmts interface.
tests/integration/watcher-fk-embeddings.test.ts Adds upsertFileHash/deleteFileHash to makeStmts fixture to satisfy the updated IncrementalStmts interface.
tests/integration/issue-1259-watch-call-resolution.test.ts Adds upsertFileHash/deleteFileHash to makeStmts fixture to satisfy the updated IncrementalStmts interface.
tests/integration/issue-1370-incremental-caller-name.test.ts Adds upsertFileHash/deleteFileHash to makeStmts fixture to satisfy the updated IncrementalStmts interface.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant P as Pipeline
    participant IN as insertNodes (Stage 5)
    participant RI as resolveImports (Stage 6)
    participant BE as buildEdges (Stage 7)
    participant CFH as commitFileHashes
    participant DB as SQLite DB

    Note over P,DB: OLD behaviour (pre-fix)
    P->>IN: insertNodes(ctx)
    IN->>DB: INSERT nodes + edges
    IN->>DB: COMMIT file_hashes (too early)
    P->>RI: resolveImports(ctx)
    P->>BE: buildEdges(ctx)
    BE-->>P: throws / interrupted
    Note over DB: hash says up to date but edges are stale

    Note over P,DB: NEW behaviour (post-fix)
    P->>IN: insertNodes(ctx)
    IN->>DB: INSERT nodes (no hash write)
    IN->>DB: DELETE hashes for removed files only
    P->>RI: resolveImports(ctx)
    P->>BE: buildEdges(ctx)
    BE-->>P: throws / interrupted
    Note over DB: hash unchanged, next build re-detects file as changed
    BE->>P: success
    P->>CFH: commitFileHashes(ctx)
    CFH->>DB: COMMIT file_hashes (edges consistent)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant P as Pipeline
    participant IN as insertNodes (Stage 5)
    participant RI as resolveImports (Stage 6)
    participant BE as buildEdges (Stage 7)
    participant CFH as commitFileHashes
    participant DB as SQLite DB

    Note over P,DB: OLD behaviour (pre-fix)
    P->>IN: insertNodes(ctx)
    IN->>DB: INSERT nodes + edges
    IN->>DB: COMMIT file_hashes (too early)
    P->>RI: resolveImports(ctx)
    P->>BE: buildEdges(ctx)
    BE-->>P: throws / interrupted
    Note over DB: hash says up to date but edges are stale

    Note over P,DB: NEW behaviour (post-fix)
    P->>IN: insertNodes(ctx)
    IN->>DB: INSERT nodes (no hash write)
    IN->>DB: DELETE hashes for removed files only
    P->>RI: resolveImports(ctx)
    P->>BE: buildEdges(ctx)
    BE-->>P: throws / interrupted
    Note over DB: hash unchanged, next build re-detects file as changed
    BE->>P: success
    P->>CFH: commitFileHashes(ctx)
    CFH->>DB: COMMIT file_hashes (edges consistent)
Loading

Reviews (4): Last reviewed commit: "Merge origin/main into fix/issue-1731-st..." | Re-trigger Greptile

Comment thread crates/codegraph-core/src/db/connection.rs Outdated
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

8 functions changed13 callers affected across 6 files

  • IncrementalStmts.upsertFileHash in src/domain/graph/builder/incremental.ts:50 (0 transitive callers)
  • IncrementalStmts.deleteFileHash in src/domain/graph/builder/incremental.ts:52 (0 transitive callers)
  • rebuildFile in src/domain/graph/builder/incremental.ts:923 (4 transitive callers)
  • runPipelineStages in src/domain/graph/builder/pipeline.ts:277 (4 transitive callers)
  • tryNativeInsert in src/domain/graph/builder/stages/insert-nodes.ts:222 (3 transitive callers)
  • insertNodes in src/domain/graph/builder/stages/insert-nodes.ts:387 (4 transitive callers)
  • commitFileHashes in src/domain/graph/builder/stages/insert-nodes.ts:458 (4 transitive callers)
  • prepareWatcherStatements in src/domain/graph/watcher.ts:19 (3 transitive callers)

….ts (#1826)

docs check acknowledged — internal dedup of an existing helper, no new
functionality, public API, or architecture change.

Impact: 1 functions changed, 22 affected
@carlos-alm

Copy link
Copy Markdown
Contributor Author

@greptileai

…fix/issue-1731-stale-edges-hash-match

Resolves an import-only conflict in incremental.ts: #1826 extracted
importNamePairs into a new shared import-utils.ts (removing the local
copy this branch's import list didn't yet have), while this branch adds
fileHash/fileStat imports for the new upsertFileHash/deleteFileHash
stmts. Both additions kept side by side.

docs check acknowledged — conflict resolution only, no new functionality.

Impact: 1 functions changed, 22 affected
Base automatically changed from fix/issue-1730-exports-renamed-import-consumers to main July 6, 2026 02:56
Resolves conflicts from stacked-branch divergence: main now has #1826's
importNamePairs dedup (import-utils.ts) merged with #1822's scoped
requireNamedNode fix and #1816/#1820's earlier fixes, all landing after
this branch's last sync. Straightforward re-application of the same
resolution patterns used for #1820/#1822/#1826's equivalent conflicts.

docs check acknowledged — conflict resolution only, no new functionality.

Impact: 6 functions changed, 16 affected
@carlos-alm carlos-alm merged commit 31dfc90 into main Jul 6, 2026
29 checks passed
@carlos-alm carlos-alm deleted the fix/issue-1731-stale-edges-hash-match branch July 6, 2026 03:21
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 6, 2026
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 deps / where --file report stale import edges even though the stored file_hashes entry matches current content

1 participant