{"record":{"id":"048ba2cfc2046026","repo":"usestrix/strix","slug":"vulnerabilities-json-at-path-is-not-a-list","errorCode":null,"errorMessage":"vulnerabilities.json at {path} is not a list","messagePattern":"vulnerabilities\\.json at (.+?) is not a list","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"strix/report/state.py","lineNumber":204,"sourceCode":"            scan_results = data.get(\"scan_results\")\n            if isinstance(scan_results, dict):\n                self.scan_results = scan_results\n                self.final_scan_result = self._format_final_scan_result(scan_results)\n            self._hydrate_llm_usage(data.get(\"llm_usage\"))\n            logger.info(\"report state hydrated run.json from %s\", run_dir)\n\n        json_path = run_dir / \"vulnerabilities.json\"\n        if json_path.exists():\n            try:\n                data = json.loads(json_path.read_text(encoding=\"utf-8\"))\n            except (OSError, json.JSONDecodeError) as exc:\n                raise RuntimeError(\n                    f\"vulnerabilities.json at {json_path} is corrupt ({exc}); \"\n                    f\"refusing to start fresh — that would overwrite prior \"\n                    f\"vulnerability MDs on disk. Inspect or delete the run dir.\",\n                ) from exc\n            if not isinstance(data, list):\n                raise RuntimeError(\n                    f\"vulnerabilities.json at {json_path} is not a list\",\n                )\n            self.vulnerability_reports = [r for r in data if isinstance(r, dict)]\n            for r in self.vulnerability_reports:\n                rid = r.get(\"id\")\n                if isinstance(rid, str):\n                    self._saved_vuln_ids.add(rid)\n            logger.info(\n                \"report state hydrated %d vulnerability report(s)\",\n                len(self.vulnerability_reports),\n            )\n\n    def add_vulnerability_report(\n        self,\n        title: str,\n        severity: str,\n        description: str | None = None,\n        impact: str | None = None,","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/report/state.py#L186-L222","documentation":"After successfully parsing vulnerabilities.json, ReportState requires the top-level JSON value to be a list (the file is an array of vulnerability report objects). Any other JSON type (object, string, number) raises RuntimeError. This guards the hydration contract before indexing saved report ids.","triggerScenarios":"vulnerabilities.json parses as valid JSON but is not an array — e.g. someone hand-edited it into an object like {\"vulnerabilities\": [...]}, or a different tool wrote an object-shaped file into the run dir.","commonSituations":"Manual editing or scripting over the run artifacts; schema drift after upgrading Strix; merging outputs from another reporting tool into the same run dir.","solutions":["Rewrite vulnerabilities.json as a JSON array of report objects: [ {...}, {...} ].","If an object with a key like 'vulnerabilities' was written, extract that array into the file root.","If uncertain of the expected shape, generate a fresh run and compare its vulnerabilities.json structure.","Delete the run dir if the run is disposable."],"exampleFix":"// before: vulnerabilities.json\n{\"vulnerabilities\": [{\"id\": \"VULN-001\"}]}\n\n// after: vulnerabilities.json\n[{\"id\": \"VULN-001\"}]","handlingStrategy":"type-guard","validationCode":"import json\nfrom pathlib import Path\n\ndef vuln_json_is_list(run_dir: Path) -> bool:\n    p = run_dir / \"vulnerabilities.json\"\n    if not p.exists():\n        return True\n    try:\n        return isinstance(json.loads(p.read_text(encoding=\"utf-8\")), list)\n    except (OSError, json.JSONDecodeError):\n        return False","typeGuard":"def is_vuln_file_shape(data: object) -> bool:\n    return isinstance(data, list) and all(isinstance(r, dict) for r in data)","tryCatchPattern":"try:\n    state = ReportState(run_dir)\nexcept RuntimeError as exc:\n    if \"is not a list\" in str(exc):\n        # repair shape: extract array from wrapper object, then retry\n        ...","preventionTips":["Keep vulnerabilities.json an array of objects; never wrap it in a keyed object.","Validate external tooling that writes into run dirs against a fresh scan's file shape.","Add a JSON-schema check in CI when artifacts are archived."],"tags":["state","json","schema","report"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}