Graphify-Labs/graphify · error · RuntimeError

could not load graph.json at {resolved_path}

Error message

could not load graph.json at {resolved_path}

What it means

Raised by _GraphContextManager._load_entry (graphify/serve.py) when the shared _load_graph call exits via SystemExit while building a cache entry. In CLI usage invalid input terminates the process, but a client-supplied project_path in the shared MCP server must become a per-request RuntimeError so the server keeps serving other graphs. It always chains the original SystemExit as __cause__.

Source

Thrown at graphify/serve.py:121

    """Thread-safe graph contexts: one pinned default plus an LRU of projects."""

    def __init__(self, max_contexts: int):
        self._max_contexts = max_contexts
        self._entries: OrderedDict[str, dict] = OrderedDict()
        self._pinned: dict[str, dict] = {}
        self._lock = threading.Lock()

    def _load_entry(self, resolved_path: str, key: tuple[int, int]) -> dict:
        """Build one entry for an already-resolved path and known file key.

        ``_load_graph`` is also used by the CLI, where invalid input terminates
        the process. A client-supplied ``project_path`` must instead become a
        tool error, so the shared MCP server can continue serving other graphs.
        """
        try:
            graph = _load_graph(resolved_path)
        except SystemExit as exc:
            raise RuntimeError(f"could not load graph.json at {resolved_path}") from exc
        # Warm the index before exposing the graph so its first query does not
        # pay the expensive build cost.
        _get_trigram_index(graph)
        communities = _communities_from_graph(graph)
        entry = {
            "key": key,
            "G": graph,
            "communities": communities,
        }
        return entry

    def load(self, resolved_path: str, *, pinned: bool = False) -> tuple[nx.Graph, dict[int, list[str]]]:
        """Return a fresh context, retaining project contexts by LRU order.

        ``resolved_path`` is resolved by the caller, making this method the
        sole owner of file statting and cache-key construction.

        ``pinned=True`` is reserved for the server's configured default graph;

View on GitHub (pinned to 7fe58b0b0f)

Solutions

  1. Inspect the __cause__ (the original SystemExit) or reproduce with graphify CLI on the same path to see the underlying reason.
  2. Fix the target project's graph: build it (/graphify), confirm graph.json exists, has .json suffix, and is under the size cap.
  3. As an MCP client, catch the tool error text and surface it to the user instead of retrying the same project_path.
  4. Server operators: validate project_path candidates (contains graphify-out/graph.json) before admitting them to the LRU.

Example fix

# before
try:
    G, comms = ctx.load(resolved_path)
except RuntimeError as e:
    pass  # reason lost

# after
try:
    G, comms = ctx.load(resolved_path)
except RuntimeError as e:
    logger.error("load failed for %s: %s", resolved_path, e.__cause__)
Defensive patterns

Strategy: try-catch

Validate before calling

def project_loadable(resolved_path: str) -> bool:
    p = Path(resolved_path)
    return p.suffix == ".json" and p.is_file()  # extension + existence pre-checks

Type guard

def is_admissible_project_path(resolved_path: str) -> bool:
    p = Path(resolved_path)
    return p.name == "graph.json" and p.is_file()

Try / catch

try:
    G, comms = ctx.load(resolved_path)
except RuntimeError as e:
    if "could not load graph.json" in str(e):
        cause = e.__cause__  # the SystemExit from _load_graph carries the real reason
        logger.error("graph load failed for %s: %s", resolved_path, cause)
        return tool_error(f"invalid graph at {resolved_path}: {cause}")
    raise

Prevention

When it happens

Trigger: An MCP tool call with a project_path whose graph.json fails validation inside _load_graph (bad extension, missing file, over-cap size, or argparse-style sys.exit paths), hitting _load_entry's except SystemExit branch during LRU cache population.

Common situations: MCP clients sending project_path values that have no graphify-out/graph.json; concurrent requests to many projects where one has a broken/oversized graph; version drift where an older _load_graph still called sys.exit on bad input.

Related errors


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