Repository navigation
Incremental --no-cluster extraction overwrites the full graph with the changed-file subset #2169
Description
Summary
After a successful full graphify extract, editing one code file and rerunning graphify extract . --code-only --no-cluster overwrites graphify-out/graph.json with only the changed file’s incremental extraction.
Unchanged nodes and edges disappear, and retained edges can point to absent endpoint nodes. --force restores the full graph.
This reproduces on upstream v8 (2fa6cd3) and PR #2134 commit 36ecb25. The PR author independently confirmed the same behavior on an unrelated 789-file TypeScript/Next.js corpus: #2134 (comment)
Environment
graphifyy 0.9.25- macOS
- TypeScript / TSX
- code-only extraction
- reproduced with and without multigraph; the minimal case below uses the default simple/raw
--no-clusterpath
Minimal reproduction
.
├── app/add/scan.tsx
└── src/components/ScanScreen.tsx
// app/add/scan.tsx
import { ScanScreen } from '../../src/components/ScanScreen';
export default ScanScreen;// src/components/ScanScreen.tsx
export function ScanScreen() {
return null;
}graphify extract . --code-only --no-cluster
# 3 nodes, 4 edges:
# imports_from, imports, contains, re_exports
printf '\n// harmless edit\n' >> app/add/scan.tsx
graphify extract . --code-only --no-cluster
# incremental scan: 1 changed, 1 unchanged
# 1 node, 3 edges
graphify extract . --code-only --no-cluster --force
# restored: 3 nodes, 4 edgesObserved result
After the incremental run:
- only
app_add_scanremains; - the unchanged
ScanScreen.tsxfile/function nodes are gone; - its
containsedge is gone; - the three retained import/re-export edges target absent, absolute-path-shaped IDs.
Representative missing targets:
private_tmp_..._src_components_scanscreen_tsx
private_tmp_..._src_components_scanscreen_scanscreen
The output is therefore both incomplete and structurally invalid.
Larger-corpus confirmation
On an Expo/React Native TypeScript corpus:
- clustered simple: 1,143 → 1,140 edges after editing
app/add/scan.tsx; - clustered edit of another TSX file: 1,143 → 1,134 edges;
--no-cluster --multigraph: 556 nodes / 1,143 edges → 1 node / 0 edges;--forcerestores the baseline.
The PR author reproduced the same pattern on a 789-file TypeScript/Next.js corpus:
- simple: 13,001 → 12,995 edges;
--no-cluster: 4,586 nodes / 15,340 edges → 9 nodes / 22 edges;- multigraph: 13,240 → 13,234 edges;
--forcerestores all baselines.
Expected behavior
Incremental extraction should merge or replace the changed file’s freshly extracted content in the existing graph while preserving unchanged nodes and edges.
For --no-cluster, it must not serialize the changed subset over the full graph.json.
Changed-file endpoint IDs should also be normalized against the extraction root so newly extracted import edges resolve to the unchanged target nodes.
Likely area
The --no-cluster path appears to write the current incremental merged payload directly. The carry-forward path currently appears limited to format conversion, while ordinary incremental changed-file runs do not carry the existing graph before replacement/deduplication.
Related
- extract no-op incremental run overwrites graph with empty graph #1347 addressed no-op incremental overwrites, but this is a changed-file run on current
v8. - hook-style relative changed_paths corrupt incremental code graph #1348 addressed hook-style relative paths, but this reproduces through ordinary
graphify extract. - Found while validating feat(cluster): cluster graphs — link multiple repos into one connected graph (+ multigraph mode) #2134; the multigraph feature itself is not the cause.
Activity
fix(extract): incremental --no-cluster merges instead of overwriting …
chore: bump to 0.9.27; changelog for #2167/#2169/#2154/#2153/#2147/#2168
test(resolution): assert canonical import-target ids in jsconfig test…
fix(extract): canonicalize regex-rescue import target ids (#2195)
safishamsi commentedon Jul 26, 2026
Fixed in v0.9.27. Incremental --no-cluster now merges the existing graph forward (same replace/prune semantics as the clustered path) instead of overwriting it, and canonicalizes cross-file edge targets; a corrupt existing graph is refused rather than overwritten. https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.27
Squashed commit of the following:
fix(extract): stamp target_file on Python imports and markdown refs s…
fix(extractors): attribute Godot cross-file stubs to the file they re…
fix(extract): canonicalize out-of-root import/include edge targets (#…
fix(extract): incremental --no-cluster merges instead of overwriting …
chore: bump to 0.9.27; changelog for Graphify-Labs#2167/Graphify-Labs…
test(resolution): assert canonical import-target ids in jsconfig test…
fix(extract): canonicalize regex-rescue import target ids (Graphify-L…
fix(extract): stamp target_file on Python imports and markdown refs s…
fix(extract): canonicalize out-of-root import/include edge targets (G…