{"record":{"id":"8ad8f05fbe0de0b6","repo":"Graphify-Labs/graphify","slug":"error-graph-is-empty-extraction-produced-no-nod-8ad8f0","errorCode":null,"errorMessage":"ERROR: Graph is empty - extraction produced no nodes.","messagePattern":"ERROR: Graph is empty - extraction produced no nodes\\.","errorType":"console","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"tools/skillgen/expected/graphify__skill-droid.md","lineNumber":416,"sourceCode":"from graphify.build import build_from_json\nfrom graphify.cluster import cluster, score_all\nfrom graphify.analyze import god_nodes, surprising_connections, suggest_questions\nfrom graphify.report import generate\nfrom graphify.export import to_json\nfrom pathlib import Path\n\nextraction = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding=\\\"utf-8\\\"))\ndetection  = json.loads(Path('graphify-out/.graphify_detect.json').read_text(encoding=\\\"utf-8\\\"))\n\n# root= mirrors the --update runbook (#1361): relativize source_file to the same\n# base so the full build and incremental --update never drift apart on re-extract.\nG = build_from_json(extraction, root='INPUT_PATH', directed=IS_DIRECTED)\n# Guard BEFORE any write: an empty extraction must not clobber a good graph.json /\n# GRAPH_REPORT.md / analysis sidecar. Check immediately after build (#1392).\nif G.number_of_nodes() == 0:\n    print('ERROR: Graph is empty - extraction produced no nodes.')\n    print('Possible causes: all files were skipped, binary-only corpus, or extraction failed.')\n    raise SystemExit(1)\ncommunities = cluster(G)\ncohesion = score_all(G, communities)\ntokens = {'input': extraction.get('input_tokens', 0), 'output': extraction.get('output_tokens', 0)}\ngods = god_nodes(G)\nsurprises = surprising_connections(G, communities)\nlabels = {cid: 'Community ' + str(cid) for cid in communities}\n# Placeholder questions - regenerated with real labels in Step 5\nquestions = suggest_questions(G, communities, labels)\n\n# Export FIRST and honor the #479 shrink-guard: to_json returns False (writing\n# nothing) when the new graph is smaller than the existing graph.json. Only write\n# GRAPH_REPORT.md + the analysis sidecar when the graph was actually written, so\n# they never describe a graph that graph.json doesn't contain (#1392).\nwrote = to_json(G, communities, 'graphify-out/graph.json')\nif not wrote:\n    print('ERROR: refused to shrink graphify-out/graph.json (existing graph has more nodes; #479).')\n    print('If this shrink is intentional (you deleted files), re-run a full build with --force.')\n    raise SystemExit(1)","sourceCodeStart":398,"sourceCodeEnd":434,"githubUrl":"https://github.com/Graphify-Labs/graphify/blob/7fe58b0b0f3873be9a21c30106b8b8527c353aa6/tools/skillgen/expected/graphify__skill-droid.md#L398-L434","documentation":"Fail-fast guard (#1392) in the graphify build pipeline: after build_from_json(extraction, root='INPUT_PATH', directed=IS_DIRECTED) reconstructs the graph from graphify-out/.graphify_extract.json, a zero-node result aborts with SystemExit(1) before graph.json, GRAPH_REPORT.md, or the analysis sidecar are touched. This protects existing artifacts from being overwritten by an empty graph.","triggerScenarios":"build_from_json() yielding G.number_of_nodes() == 0 because the extract file contains no nodes — all files skipped, binary-only corpus, or failed extraction. The root='INPUT_PATH' relativization can also drop every node if INPUT_PATH disagrees with the paths stored in the extract.","commonSituations":"Empty or truncated .graphify_extract.json after an extraction API failure; input dir with only skipped file types; ignore rules matching everything; INPUT_PATH mismatch with the extraction-time root.","solutions":["Inspect graphify-out/.graphify_extract.json for node content; empty means the failure is upstream in extraction.","Make sure INPUT_PATH equals the root used during extraction so source_file relativization keeps every node.","Review extractor skip/ignore rules for over-broad matches.","Re-run extraction, then this build step — prior artifacts are preserved by the guard."],"exampleFix":"# before: zero nodes abort the build\nG = build_from_json(extraction, root='INPUT_PATH', directed=IS_DIRECTED)\nif G.number_of_nodes() == 0:\n    print('ERROR: Graph is empty - extraction produced no nodes.')\n    raise SystemExit(1)\n\n# after: pre-validate extraction payload and root agreement\nfiles = extraction.get('files', [])\nassert files and any(f.get('nodes') for f in files), 'nothing extracted - fix extraction step first'\nG = build_from_json(extraction, root='INPUT_PATH', directed=IS_DIRECTED)\nassert G.number_of_nodes() > 0","handlingStrategy":"validation","validationCode":"import json\nfrom pathlib import Path\n\nextraction = json.loads(Path('graphify-out/.graphify_extract.json').read_text(encoding='utf-8'))\nfiles = extraction.get('files', [])\nassert files and any(f.get('nodes') for f in files), 'empty extraction - fix upstream before build'\nG = build_from_json(extraction, root='INPUT_PATH', directed=IS_DIRECTED)\nassert G.number_of_nodes() > 0, 'root mismatch or empty extract produced 0 nodes'","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pre-validate the extraction sidecar for non-empty nodes before building.","Pass the same root= to build_from_json as the extraction step used.","Monitor extraction skip counts to catch all-files-skipped runs early.","Keep input directories populated with extractable text files."],"tags":["graphify","extraction","empty-graph","fail-fast","build"],"backgroundTag":null,"analyzedSha":"7fe58b0b0f3873be9a21c30106b8b8527c353aa6","analyzedAt":"2026-08-14T19:23:21.323Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}