{"record":{"id":"6438a40322cb98b3","repo":"headroomlabs-ai/headroom","slug":"invalid-pipeline-config-toml-0","errorCode":null,"errorMessage":"invalid pipeline config TOML: {0}","messagePattern":"invalid pipeline config TOML: (.+?)","errorType":"exception","errorClass":"ConfigError","httpStatus":null,"severity":"error","filePath":"crates/headroom-core/src/transforms/pipeline/config.rs","lineNumber":221,"sourceCode":"pub struct ProseFieldConfig {\n    pub min_bytes: usize,\n    pub min_segments: usize,\n    pub target_ratio: f64,\n}\n\n/// Knobs for the [`crate::transforms::pipeline::offloads::DiffNoise`]\n/// offload. Lockfile suffixes are matched against the new-file path\n/// at the end of each `diff --git` header.\n#[derive(Debug, Clone, Deserialize)]\npub struct DiffNoiseConfig {\n    pub min_lines: usize,\n    pub lockfile_suffixes: Vec<String>,\n    pub drop_whitespace_only_hunks: bool,\n}\n\n#[derive(Debug, thiserror::Error)]\npub enum ConfigError {\n    #[error(\"invalid pipeline config TOML: {0}\")]\n    Parse(#[from] toml::de::Error),\n    #[error(\"could not read pipeline config file: {0}\")]\n    Io(std::io::Error),\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn from_default_str_does_not_panic() {\n        // Embedded TOML must always deserialize cleanly.\n        let _ = PipelineConfig::from_default_str();\n    }\n\n    #[test]\n    fn defaults_match_documented_thresholds() {\n        let cfg = PipelineConfig::default();","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/crates/headroom-core/src/transforms/pipeline/config.rs#L203-L239","documentation":"The body parsed as JSON but the top-level value is not an object — e.g. a JSON array, string, or number (helpers.py:2519). The Anthropic/OpenAI-style request schema is a JSON object, so arrays like '[{...}]' or bare strings are rejected with the actual type name embedded in the message.","triggerScenarios":"POSTing '[{\"model\": ...}]' (array-wrapped request) to a proxied route; sending a JSON string '\"hello\"' or a bare number; a client that wraps payloads for batching APIs and reuses the same code against headroom.","commonSituations":"Porting code from batch APIs that accept arrays; accidental double-serialization (json.dumps applied twice yields a string); test fixtures copied from other APIs.","solutions":["Send a single JSON object at the top level: {\"model\": ..., \"messages\": [...]}","Check for double serialization: if your payload is a str after dumps, you serialized twice","Validate the body client-side: isinstance(payload, dict) before sending"],"exampleFix":"# before\nrequests.post(url, json=[{\"model\": \"claude\", \"messages\": msgs}])\n\n# after\nrequests.post(url, json={\"model\": \"claude\", \"messages\": msgs})","handlingStrategy":"type-guard","validationCode":"payload = json.loads(text)\nif not isinstance(payload, dict):\n    raise TypeError(f\"expected a JSON object, got {type(payload).__name__}\")","typeGuard":"def is_request_object(value: object) -> bool:\n    return isinstance(value, dict)","tryCatchPattern":"try:\n    payload = await _read_json_body(request)\nexcept ValueError as exc:\n    return JSONResponse({\"error\": str(exc)}, status_code=400)","preventionTips":["Use request libraries' json= parameter so serialization happens once","Assert dict payloads in client-side test fixtures"],"tags":["json","validation","request-body"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}