{"record":{"id":"9178f62795ab6dff","repo":"Graphify-Labs/graphify","slug":"cannot-read-graph-path-for-incremental-merge-e","errorCode":null,"errorMessage":"Cannot read {graph_path} for incremental merge: {exc}. Delete the file and run a full rebuild.","messagePattern":"Cannot read (.+?) for incremental merge: (.+?)\\. Delete the file and run a full rebuild\\.","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"graphify/build.py","lineNumber":1432,"sourceCode":"    The latter rebuilds an undirected nx.Graph and then enumerating\n    edges() yields endpoints based on node insertion order, which\n    silently flips directional edges (e.g. `calls`) when the callee\n    was inserted before the caller. The _src/_tgt direction-preserving\n    attrs are popped before saving in export.py, so going through the\n    NetworkX round-trip loses direction permanently (#760).\n\n    Returns None when the file does not exist. Raises RuntimeError when it\n    exists but cannot be parsed — callers must refuse to overwrite rather\n    than silently replace a possibly-recoverable graph.\n    \"\"\"\n    if not graph_path.exists():\n        return None\n    from graphify.security import check_graph_file_size_cap\n    check_graph_file_size_cap(graph_path)\n    try:\n        data = json.loads(graph_path.read_text(encoding=\"utf-8\"))\n    except (json.JSONDecodeError, OSError) as exc:\n        raise RuntimeError(\n            f\"Cannot read {graph_path} for incremental merge: {exc}. \"\n            \"Delete the file and run a full rebuild.\"\n        ) from exc\n    links_key = \"links\" if \"links\" in data else \"edges\"\n    nodes = list(data.get(\"nodes\", []))\n    edges = list(data.get(links_key, []))\n    # Backfill tier provenance on legacy items (#2334): _origin is stamped at\n    # extraction time only (extract.py for AST, and the semantic path never\n    # stamps), so pre-0.9.16 graphs and externally-merged fragments carry\n    # unstamped items. Stamp them via the _is_ast_tier shape fallback so the\n    # graph self-heals on the next write and every downstream tier decision\n    # (build_merge replace, watch reconcile) reads an explicit marker.\n    for item in nodes:\n        if isinstance(item, dict):\n            item.setdefault(\"_origin\", \"ast\" if _is_ast_tier(item) else \"semantic\")\n    for item in edges:\n        if isinstance(item, dict):\n            item.setdefault(\"_origin\", \"ast\" if _is_ast_tier(item) else \"semantic\")","sourceCodeStart":1414,"sourceCodeEnd":1450,"githubUrl":"https://github.com/Graphify-Labs/graphify/blob/7fe58b0b0f3873be9a21c30106b8b8527c353aa6/graphify/build.py#L1414-L1450","documentation":"The generic (cross-platform) graphify skill template's empty-graph guard (tools/skillgen/expected/graphify__skill.md:419). Immediately after build_from_json() constructs the networkx graph from .graphify_extract.json, a zero node count aborts with SystemExit(1) before cluster(), to_json(), or any file write runs — so a bad extraction cannot destroy existing outputs (#1392).","triggerScenarios":"build_from_json(extraction, root='INPUT_PATH', directed=IS_DIRECTED) returns a graph with 0 nodes: the extraction JSON has an empty node list, all input files were skipped during detection, or extraction itself failed silently upstream.","commonSituations":"Running /graphify on an empty or binary-only directory; a previous pipeline step crashed after truncating .graphify_extract.json to an empty structure; over-aggressive skip/exclude rules; wrong working directory so relative INPUT_PATH resolves to nothing.","solutions":["Confirm graphify-out/.graphify_extract.json has a non-empty nodes array; re-run extraction if not.","Check the detection sidecar (.graphify_detect.json) for what was skipped and why; ensure the target tree contains text source files.","Remove the stale extract/detect sidecars and re-run the full build so empty artifacts are not carried forward.","Run from the repository root (or the directory the skill expects) so INPUT_PATH resolves to the intended tree."],"exampleFix":"# before\n/graphify ./build-output   # binary-only, zero nodes\n\n# after\nrm graphify-out/.graphify_extract.json graphify-out/.graphify_detect.json\n/graphify ./src","handlingStrategy":"validation","validationCode":"import json\nfrom pathlib import Path\n\nextract = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding='utf-8'))\nassert extract.get('nodes'), 'empty nodes list - abort before build_from_json'\nprint('precheck passed:', len(extract['nodes']), 'nodes')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Assert the extraction sidecar is non-empty before building.","Run the skill from the repository root so INPUT_PATH and sidecar paths resolve correctly.","Ensure the target tree has extractable text files and skip rules are not over-broad.","Clear sidecars when changing the input path."],"tags":["graphify","empty-graph","fail-fast","skillgen","extraction"],"backgroundTag":null,"analyzedSha":"7fe58b0b0f3873be9a21c30106b8b8527c353aa6","analyzedAt":"2026-08-14T19:23:21.323Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}