{"record":{"id":"5386473bb280e8e6","repo":"usestrix/strix","slug":"run-json-at-path-is-not-an-object","errorCode":null,"errorMessage":"run.json at {path} is not an object","messagePattern":"run\\.json at (.+?) is not an object","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"strix/report/writer.py","lineNumber":105,"sourceCode":"    try:\n        lexer = guess_lexer(code)\n    except ClassNotFound:\n        return \"python\"\n    if isinstance(lexer, TextLexer) or not lexer.aliases:\n        return \"python\"\n    return str(lexer.aliases[0])\n\n\ndef read_run_record(run_dir: Path) -> dict[str, Any]:\n    path = run_record_path(run_dir)\n    if not path.exists():\n        return {}\n    try:\n        data = json.loads(path.read_text(encoding=\"utf-8\"))\n    except (OSError, json.JSONDecodeError) as exc:\n        raise RuntimeError(f\"run.json at {path} is unreadable: {exc}\") from exc\n    if not isinstance(data, dict):\n        raise TypeError(f\"run.json at {path} is not an object\")\n    return data\n\n\ndef write_run_record(run_dir: Path, run_record: dict[str, Any]) -> None:\n    _atomic_write_text(\n        run_record_path(run_dir),\n        json.dumps(run_record, ensure_ascii=False, indent=2, default=str),\n    )\n\n\ndef write_executive_report(run_dir: Path, final_scan_result: str) -> None:\n    path = run_dir / \"penetration_test_report.md\"\n    with path.open(\"w\", encoding=\"utf-8\") as f:\n        f.write(\"# Security Penetration Test Report\\n\\n\")\n        f.write(f\"**Generated:** {datetime.now(UTC).strftime('%Y-%m-%d %H:%M:%S UTC')}\\n\\n\")\n        f.write(f\"{final_scan_result}\\n\")\n    logger.info(\"Saved final penetration test report to: %s\", path)\n","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/report/writer.py#L87-L123","documentation":"After read_run_record() parses run.json, it enforces that the top-level value is a JSON object (dict) before returning it; anything else raises TypeError. Callers rely on dict access (data.get(...)) for status, llm_usage.cost, etc., so a non-object file is a hard schema violation.","triggerScenarios":"run.json contains a JSON array, string, or number at top level. Typically caused by manual editing, a redirected output overwriting the file (e.g. `cmd > run.json` writing non-JSON), or a foreign tool writing into the run dir.","commonSituations":"Operators scripting over run.json and accidentally replacing it; log redirection mistakes; artifacts produced by a different/older schema.","solutions":["Inspect run.json and rewrite it as a single JSON object with the expected keys (status, llm_usage, scan_results, ...).","If unrecoverable, delete the run dir or regenerate the record with a new scan.","Audit any script that writes into strix_runs/<run>/ and keep run.json write-protected from ad-ho tooling."],"exampleFix":"// before: run.json\n[\"status\", \"completed\"]\n\n// after: run.json\n{\"status\": \"completed\", \"llm_usage\": {\"cost\": 0.0}}","handlingStrategy":"type-guard","validationCode":"import json\nfrom pathlib import Path\n\ndef run_json_is_object(run_dir: Path) -> bool:\n    p = run_dir / \"run.json\"\n    if not p.exists():\n        return True\n    try:\n        return isinstance(json.loads(p.read_text(encoding=\"utf-8\")), dict)\n    except (OSError, json.JSONDecodeError):\n        return False","typeGuard":"def is_run_record(data: object) -> bool:\n    return isinstance(data, dict)","tryCatchPattern":"from strix.report.writer import read_run_record\ntry:\n    record = read_run_record(run_dir)\nexcept TypeError:\n    # schema violation: run.json top level must be an object\n    raise","preventionTips":["Never redirect command output into run.json (`cmd > run.json` is a classic footgun).","Script read access via read_run_record() instead of ad-hoc json.load so you get typed errors.","Keep third-party tools from writing into strix_runs/<run>/."],"tags":["json","schema","run-record","type-error"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}