{"record":{"id":"1af6a2e0d1eb60c5","repo":"headroomlabs-ai/headroom","slug":"doc-json-must-be-json-e","errorCode":null,"errorMessage":"doc_json must be JSON: {e}","messagePattern":"doc_json must be JSON: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/headroom-py/src/lib.rs","lineNumber":868,"sourceCode":"    /// return the compacted document as JSON.\n    ///\n    /// The walker recursively descends through objects, arrays, and\n    /// strings; tabular sub-arrays become rendered CSV+schema strings,\n    /// long opaque blobs become `<<ccr:HASH,KIND,SIZE>>` markers (with\n    /// originals stashed in this crusher's CCR store, so `ccr_get`\n    /// resolves them).\n    ///\n    /// Distinct from `crush_array_json`: this is the lossless walker\n    /// pass without per-array lossy crushing — useful when the caller\n    /// wants document-shape compaction (forms, configs, mixed records)\n    /// rather than statistical row drop.\n    fn compact_document_json(&self, py: Python<'_>, doc_json: &str) -> String {\n        // Heavy: JSON parse + recursive walker + tabular compaction +\n        // re-serialize. None of it touches Python; release the GIL.\n        let doc_json = doc_json.to_string();\n        py.detach(|| {\n            let parsed: serde_json::Value = serde_json::from_str(&doc_json)\n                .unwrap_or_else(|e| panic!(\"doc_json must be JSON: {e}\"));\n            let mut dc = DocumentCompactor::new().with_config(CompactConfig {\n                classify: ClassifyConfig {\n                    emit_opaque_markers: self.inner.config.opaque_markers_enabled(),\n                    ..ClassifyConfig::default()\n                },\n                ..CompactConfig::default()\n            });\n            if let Some(store) = self.inner.ccr_store() {\n                dc = dc.with_ccr_store(store.clone());\n            }\n            let out = dc.compact(parsed);\n            serde_json::to_string(&out).expect(\"serialize compacted document\")\n        })\n    }\n\n    /// Look up an original payload by CCR hash.\n    ///\n    /// When the lossy path drops rows, it stashes the **full original**","sourceCodeStart":850,"sourceCodeEnd":886,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/crates/headroom-py/src/lib.rs#L850-L886","documentation":"Python-binding panic from compact_document_json: the doc_json argument is not parseable by serde_json. Same contract-violation pattern as items_json — the Rust closure panics (surfacing as a Python RuntimeError) rather than returning a fallback, per the no-silent-fallback project rule.","triggerScenarios":"Calling compact_document_json(doc_json) with str()/repr() of a Python dict, a truncated file read, empty string, or otherwise malformed JSON text.","commonSituations":"Forgetting json.dumps on the dict; slurping a truncated/partially-written JSON file; passing text with a BOM or encoding damage; passing an empty string for 'no document'.","solutions":["Serialize with json.dumps(doc) before calling.","Validate provenance: json.loads(doc_json) on the Python side first, or fix the upstream writer that produced truncated JSON.","Handle the empty case explicitly (pass '{}' or skip the call) instead of ''.","Catch RuntimeError around the call if malformed input is possible in production paths, and log the offending string length/prefix for diagnosis."],"exampleFix":"# before\nout = hr.compact_document_json(str(doc))\n\n# after\nimport json\nout = hr.compact_document_json(json.dumps(doc))","handlingStrategy":"validation","validationCode":"# Validate before calling\nimport json\ndef safe_compact(hr, doc_json: str) -> str:\n    try:\n        json.loads(doc_json)\n    except json.JSONDecodeError as e:\n        raise ValueError(f\"doc_json is not valid JSON: {e}\") from e\n    return hr.compact_document_json(doc_json)","typeGuard":"def is_json_string(s: str) -> bool:\n    try:\n        json.loads(s)\n        return True\n    except (json.JSONDecodeError, TypeError):\n        return False","tryCatchPattern":"try:\n    out = hr.compact_document_json(json.dumps(doc))\nexcept RuntimeError as e:\n    log.error(\"compact_document_json failed: %s\", e)\n    raise","preventionTips":["json.dumps dicts before passing; never str()/repr().","For file-sourced JSON, parse once in Python and re-dump to normalize.","Pass '{}' rather than '' for an empty document."],"tags":["python","json","pyo3","ffi","validation"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}