abhigyanpatwari/GitNexus · critical

Streaming PDG manifest collides with a structural relationsh

Error message

Streaming PDG manifest collides with a structural relationship CSV for pair "${pairKey}" — a PDG edge leaked into the in-memory graph during a streamed emit.

What it means

Rel-side twin of the node collision guard, in loadGraphToLbug (gitnexus/src/core/lbug/lbug-adapter.ts:1217). During a streamed PDG emit, PDG edges (CFG / REACHING_DEF / CDG / POST_DOMINATE / TAINTED / SANITIZES) go to per-pair CSV writers and the in-memory graph holds none. Before node COPY commits, each manifest pair (e.g. BasicBlock|BasicBlock) is merged into the COPY plan; a pair that already exists in the structural csvResult means a PDG edge leaked into the in-memory graph. The run aborts rather than load corrupt/duplicated edge data.

Source

Thrown at gitnexus/src/core/lbug/lbug-adapter.ts:1217

        { err: nodeCopyError },
        '[lbug-load] node COPY also failed while relationship emit was failing',
      );
    }
    throw emitErr;
  }
  const tCsv = mark();

  // Merge the streamed PDG-emit per-pair rel CSVs (#2202) into the COPY plan —
  // collision-guarded. Done BEFORE node COPY so the serial escape hatch detects a
  // manifest/structural pair collision before committing any node rows (legacy
  // parity with the pre-overlap path), and the overlap path detects it as early
  // as csvResult is available. When a manifest is present, streaming was on and
  // the in-memory graph held zero BasicBlocks, so a structural collision means a
  // streaming-invariant violation — fail loudly rather than load corrupt data.
  if (pdgEmitManifest) {
    for (const [pairKey, meta] of pdgEmitManifest.relsByPair) {
      if (csvResult.relsByPair.has(pairKey)) {
        throw new Error(
          `Streaming PDG manifest collides with a structural relationship CSV for pair ` +
            `"${pairKey}" — a PDG edge leaked into the in-memory graph during a streamed emit.`,
        );
      }
      csvResult.relsByPair.set(pairKey, meta);
      csvResult.totalValidRels += meta.rows;
    }
  }

  // Serial path: all CSVs are on disk and node COPY has not started — start it
  // here so the barrier below blocks on it exactly as the legacy path did.
  if (SERIAL) beginNodeCopy(csvResult.nodeFiles);

  // FK barrier: node rows must exist before the relationship COPY resolves their
  // endpoints. In overlap mode most of node COPY was hidden behind rel emit, so
  // this await is the *residual* node-COPY time (≈0 when fully overlapped).
  if (nodeCopyPromise) await nodeCopyPromise;
  if (nodeCopyError) {

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Re-run the analyze — the dirty flag forces a full rebuild on the next run
  2. If reproducible, report a GitNexus bug noting the colliding pair key from the message
  3. When adding a PDG edge type, add it to PDG_EDGE_TYPES in pdg-emit-sink.ts so it streams instead of leaking into the structural emit
  4. Verify no emit code path calls real.addRelationship for a PDG-typed edge during streamed runs

Example fix

// before — PDG edge stored on the real graph (leaks into the structural CSV emit)
realGraph.addRelationship(cfgEdge);

// after — PDG-typed edges go through the sink's streamed rel writers
pdgSink.addRelationship(cfgEdge);
Defensive patterns

Strategy: try-catch

Validate before calling

// before load, assert no PDG-typed edges remain in the in-memory graph:
const PDG_TYPES = new Set(['CFG', 'REACHING_DEF', 'CDG', 'POST_DOMINATE', 'TAINTED', 'SANITIZES']);
let pdgInMemory = 0;
graph.forEachRelationshipFields((_s, _t, type) => {
  if (PDG_TYPES.has(type)) pdgInMemory++;
});
if (pdgEmitManifest && pdgInMemory > 0) {
  throw new Error(`emit bug: ${pdgInMemory} PDG edge(s) bypassed the streaming sink`);
}

Type guard

const isPdgManifestRelCollision = (e: unknown): boolean =>
  e instanceof Error &&
  e.message.includes('Streaming PDG manifest collides with a structural relationship CSV');

Try / catch

try {
  await loadGraphToLbug(graph, repoPath, storagePath, onProgress, manifest);
} catch (e) {
  if (isPdgManifestRelCollision(e)) {
    // a PDG edge leaked into the structural emit — full rebuild, no retry
    await markIndexDirtyAndRebuild(repoPath);
  }
  throw e;
}

Prevention

When it happens

Trigger: loadGraphToLbug called with a pdgEmitManifest whose relsByPair contains a pairKey that streamAllCSVsToDisk also emitted — caused by a PDG-typed relationship added to the real graph instead of the sink (a new PDG edge type missing from PDG_EDGE_TYPES, or an emit path calling real.addRelationship directly).

Common situations: Adding a new PDG relationship type without registering it in PdgEmitSink's PDG_EDGE_TYPES; forks that store PDG edges in memory for read-back during streamed runs; version upgrades that reorder emit passes.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20). Data as JSON: /api/errors/8bdc23c39563f8ba. Report an issue: GitHub.