{"record":{"id":"81344faeb9f21fdb","repo":"abhigyanpatwari/GitNexus","slug":"streaming-pdg-manifest-collides-with-a-structural","errorCode":null,"errorMessage":"Streaming PDG manifest collides with a structural node CSV for \"${table}\" — the in-memory graph should hold zero ${table} nodes when streaming. A ${table} node leaked into the graph during a streamed emit.","messagePattern":"Streaming PDG manifest collides with a structural node CSV for \"(.+?)\" — the in-memory graph should hold zero (.+?) nodes when streaming\\. A (.+?) node leaked into the graph during a streamed emit\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"gitnexus/src/core/lbug/lbug-adapter.ts","lineNumber":1151,"sourceCode":"\n  // The single writable connection (LadybugDB is single-writer). Captured as a\n  // const so the node-COPY closure has a non-null reference — TS cannot narrow\n  // the reassignable module-level `conn` across the callback boundary.\n  const writeConn = conn;\n  const validTables = new Set<string>(NODE_TABLES as readonly string[]);\n\n  // Merge the streamed PDG-emit node CSVs (#2202) into a node-file map. Collision\n  // guard: a BasicBlock in the in-memory graph during a streamed run is an\n  // invariant violation (streamAllCSVsToDisk would also emit basicblock.csv), so\n  // fail loudly rather than drop rows (#2202 review #3). Runs at the node-phase\n  // boundary so the manifest BasicBlock table COPYs with the structural CSVs.\n  const mergeManifestNodeFiles = (\n    nodeFilesMap: Map<NodeTableName, { csvPath: string; rows: number }>,\n  ): void => {\n    if (!pdgEmitManifest) return;\n    for (const [table, meta] of pdgEmitManifest.nodeFiles) {\n      if (nodeFilesMap.has(table)) {\n        throw new Error(\n          `Streaming PDG manifest collides with a structural node CSV for \"${table}\" — ` +\n            `the in-memory graph should hold zero ${table} nodes when streaming. ` +\n            `A ${table} node leaked into the graph during a streamed emit.`,\n        );\n      }\n      nodeFilesMap.set(table, meta);\n    }\n  };\n\n  // Node COPY is the only DB write that can overlap relationship CSV emit: the\n  // rel pass writes new rel_*.csv files and never touches `conn`, while node COPY\n  // uses `conn` and never touches the rel files. We start node COPY at the\n  // node-phase boundary and let the rel pass run concurrently — the only\n  // single-writer-safe parallelism (#2203). The rel COPY still waits for node\n  // COPY (FK precondition), so the DB load order is unchanged.\n  let nodeCopyPromise: Promise<void> | undefined;\n  let nodeCopyError: unknown;\n  const beginNodeCopy = (","sourceCodeStart":1133,"sourceCodeEnd":1169,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/core/lbug/lbug-adapter.ts#L1133-L1169","documentation":"Invariant guard in loadGraphToLbug's mergeManifestNodeFiles (gitnexus/src/core/lbug/lbug-adapter.ts:1151). When an analyze runs with the streaming PDG sink (#2202), BasicBlock nodes are routed straight to CSV on disk and the in-memory KnowledgeGraph must hold zero of them. At load time the streamed manifest is merged into the node-COPY plan; if the structural emit (streamAllCSVsToDisk) ALSO produced a CSV for the same table, a BasicBlock leaked into the real graph during the streamed emit. The run fails loudly instead of double-COPYing or silently dropping rows.","triggerScenarios":"Calling loadGraphToLbug(graph, ..., pdgEmitManifest) where graph still contains BasicBlock nodes — i.e. some emit path added a BasicBlock directly to the real KnowledgeGraph instead of routing it through the PdgEmitSink façade (addNode on the unwrapped graph, a new PDG emit phase that bypasses the sink, or a regression in how runScopeResolution threads the sink into per-language passes).","commonSituations":"Upgrading GitNexus to a version where a new or changed PDG emit path writes BasicBlocks outside PdgEmitSink; forked emit loops that pass the real graph; scale large enough that streaming activates and exposes routing that was harmless in whole-graph mode.","solutions":["Re-run the analyze — the aborted run left the crash-recovery dirty flag, so the next run performs a clean full rebuild","If it reproduces on the same repo, capture the stack and report a GitNexus bug: a BasicBlock write bypassed the streaming sink","If you maintain emit code, route every BasicBlock addNode through the PdgEmitSink façade so it lands in the streamed CSV, never in the in-memory graph","As a fork mitigation, run the emit without the streaming sink so the whole-graph path owns the BasicBlock CSV"],"exampleFix":"// before — writes a BasicBlock into the real graph during a streamed emit\nrealGraph.addNode(basicBlockNode);\n\n// after — route it through the sink so it lands in the streamed basicblock.csv\npdgSink.addNode(basicBlockNode);","handlingStrategy":"try-catch","validationCode":"// before loadGraphToLbug, when a streamed manifest is present:\nconst leaked = [...graph.iterNodes()].filter((n) => n.label === 'BasicBlock');\nif (pdgEmitManifest && leaked.length > 0) {\n  throw new Error(\n    `emit bug: ${leaked.length} BasicBlock(s) bypassed the streaming sink`,\n  );\n}","typeGuard":"const isPdgManifestNodeCollision = (e: unknown): boolean =>\n  e instanceof Error &&\n  e.message.includes('Streaming PDG manifest collides with a structural node CSV');","tryCatchPattern":"try {\n  await loadGraphToLbug(graph, repoPath, storagePath, onProgress, manifest);\n} catch (e) {\n  if (isPdgManifestNodeCollision(e)) {\n    // invariant bug — do NOT retry the same graph; force a clean full rebuild\n    await markIndexDirtyAndRebuild(repoPath);\n  }\n  throw e;\n}","preventionTips":["Never add BasicBlock nodes to the real KnowledgeGraph during a streamed --pdg emit — route all writes through the PdgEmitSink façade","Register every new PDG edge type in PdgEmitSink's PDG_EDGE_TYPES so it streams instead of landing in memory","Run the #2202 differential fingerprint test after any change to emit routing"],"tags":["gitnexus","pdg","streaming","invariant-violation","csv","knowledge-graph"],"backgroundTag":"double-write-collision","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}