Skip to content

traverseBFS: same target enqueued via two edges; second dequeue skips edge, producing incomplete edge set #1090

Description

@inth3shadows

File: src/graph/traversal.ts
Lines: 101 (inner loop visited check), 110–111 (nodes.set/queue.push), 69–77 (dequeue visited check and edge push)

Defect: The inner for loop in traverseBFS (L99–112) guards on visited.has(nextNodeId) (L101) before enqueuing. visited is only updated at L72 — inside the outer while body, after dequeuing. This means a node reachable from the current node via two different edges (e.g., two calls edges or a calls + references edge to the same target) is enqueued twice. When the queue is later drained: the first dequeue marks the node visited and pushes edge1 into edges; the second dequeue hits visited.has → continue, dropping edge2. The resulting Subgraph.edges array is incomplete.

Failure trace:

  • Node A has two edges to B: e1 (A→B, kind:calls) and e2 (A→B, kind:references).
  • Processing A: adjacentEdges = [e1, e2].
    • e1: !visited.has("B") = true → nodes.set(B), queue.push({B, e1}).
    • e2: !visited.has("B") = true (B still not in visited) → nodes.set(B) (no-op), queue.push({B, e2}).
    • Queue: [{B,e1}, {B,e2}].
  • Dequeue {B,e1}: !visited.has("B")visited.add("B"), edges.push(e1).
  • Dequeue {B,e2}: visited.has("B")continue. e2 never pushed to edges.
  • Final edges is missing e2 — a real A→B relationship is invisible to callers.

Expected fix:
Track which node IDs have already been enqueued (separately from visited) and skip re-enqueueing the same node:

const queued = new Set<string>();
queued.add(startNode.id);
// …
for (const adjEdge of adjacentEdges) {
  const nextNodeId = ;
  if (visited.has(nextNodeId) || queued.has(nextNodeId)) continue;
  
  queued.add(nextNodeId);
  nodes.set(nextNode.id, nextNode);
  queue.push({ node: nextNode, edge: adjEdge, depth: depth + 1 });
}

This ensures each node is enqueued exactly once (preserving BFS semantics) while still recording the first edge to it. If multi-edge completeness is required, edges should be collected separately from the queue and all edges to a node merged before or after traversal.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions