Graphify-Labs/graphify · error · ValueError

deduplicate_entities: nodes span multiple repos {sorted(repo

Error message

deduplicate_entities: nodes span multiple repos {sorted(repos_seen)!r}. Cross-project dedup is disabled — run dedup per-repo before merging.

What it means

Graph-existence preflight in the /graphify path command of the devin fragment (tools/skillgen/fragments/core/devin.md:1196). Shortest-path traversal needs graphify-out/graph.json (loaded via json_graph.node_link_graph(data, edges='links')); the guard exits 1 up front when the file is absent.

Source

Thrown at graphify/dedup.py:489

    """Deduplicate near-identical entities in a knowledge graph.

    Args:
        nodes: list of node dicts with at minimum {"id": str, "label": str}
        edges: list of edge dicts with {"source": str, "target": str, ...}
        communities: mapping of node_id -> community_id (from cluster())
        dedup_llm_backend: if set, use LLM to resolve ambiguous pairs
        root: scan root; ID-collision ranking judges source paths relative to
            it so path form and checkout location cannot flip the survivor (#2532)

    Returns:
        (deduped_nodes, deduped_edges) with edges rewired to survivors
    """
    # Guard: cross-project dedup is not supported — nodes from different repos
    # share label names by coincidence and must never be merged by string similarity.
    # If you need to dedup a global graph, run deduplicate_entities per-repo first.
    repos_seen = {n.get("repo") for n in nodes if n.get("repo")}
    if len(repos_seen) > 1:
        raise ValueError(
            f"deduplicate_entities: nodes span multiple repos {sorted(repos_seen)!r}. "
            f"Cross-project dedup is disabled — run dedup per-repo before merging."
        )

    if len(nodes) <= 1:
        return nodes, edges

    # Resolve the scan root once: _collision_rank ranks each node's source_file
    # relative to it, so an absolute stored path and its repo-relative twin rank
    # identically and lifecycle markers in the checkout location's own segments
    # cannot flip the survivor (#2532).
    try:
        root_resolved: Path | None = Path(root).resolve() if root else None
    except Exception:
        root_resolved = None

    # Pre-deduplicate: one node per ID. The survivor is the node that *defines* the
    # ID (its source_file is the file the ID encodes), not merely the first seen —

View on GitHub (pinned to 7fe58b0b0f)

Solutions

  1. Build the graph with /graphify <path> before running path queries.
  2. Verify cwd is the directory holding graphify-out/graph.json.
  3. Fix any earlier build failure so graph.json actually gets written.
  4. Rebuild if the graph output was cleaned.

Example fix

# before
/graphify path router handler
# ERROR: No graph found ...

# after
/graphify .
/graphify path router handler
Defensive patterns

Strategy: validation

Validate before calling

from pathlib import Path

if not Path('graphify-out/graph.json').is_file():
    raise SystemExit('no graph - run /graphify <path> before path queries')
print('ok')

Prevention

When it happens

Trigger: Running /graphify path between two concepts in a workspace where graphify-out/graph.json does not exist — never built, wrong cwd, deleted, or a failed prior build never reached export.

Common situations: Querying a fresh clone; relative-path miss because the agent runs in a subdirectory; cleanup removed graphify-out/; previous build failed at extraction or shrink guard.

Related errors


AI-assisted analysis of Graphify-Labs/graphify@7fe58b0b0f (2026-08-14). Data as JSON: /api/errors/fef0ee32e13a7d8a. Report an issue: GitHub.