{"record":{"id":"9256a7f88eccc2d2","repo":"Kuberwastaken/claurst","slug":"generationconfig-must-be-an-object","errorCode":null,"errorMessage":"generationConfig must be an object","messagePattern":"generationConfig must be an object","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-rust/crates/api/src/providers/request_options.rs","lineNumber":74,"sourceCode":"        \"temperature\",\r\n        \"thinkingConfig\",\r\n        \"topK\",\r\n        \"topP\",\r\n    ];\r\n\r\n    let Some(body_obj) = body.as_object_mut() else {\r\n        return;\r\n    };\r\n    let Some(options_obj) = provider_options.as_object() else {\r\n        return;\r\n    };\r\n\r\n    let generation_config = body_obj\r\n        .entry(\"generationConfig\".to_string())\r\n        .or_insert_with(|| Value::Object(Map::new()));\r\n    let generation_config_obj = generation_config\r\n        .as_object_mut()\r\n        .expect(\"generationConfig must be an object\");\r\n    let mut root_entries: Vec<(String, Value)> = Vec::new();\r\n\r\n    for (key, value) in options_obj {\r\n        if GENERATION_CONFIG_KEYS.contains(&key.as_str()) {\r\n            generation_config_obj.insert(key.clone(), value.clone());\r\n        } else {\r\n            root_entries.push((key.clone(), value.clone()));\r\n        }\r\n    }\r\n\r\n    for (key, value) in root_entries {\r\n        body_obj.insert(key, value);\r\n    }\r\n}\r\n\r\npub(crate) fn merge_bedrock_options(body: &mut Value, provider_options: &Value) {\r\n    let Some(body_obj) = body.as_object_mut() else {\r\n        return;\r","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/Kuberwastaken/claurst/blob/b0637c97ec34144387cbf2f74f65df6d16a6cef1/src-rust/crates/api/src/providers/request_options.rs#L56-L92","documentation":"This panic asserts an internal invariant while merging Google provider request options into a request body: after `or_insert_with(|| Value::Object(...))`, the `generationConfig` entry must be a JSON object, so `as_object_mut()` is expected to succeed. It can only fire if the body already contained a `generationConfig` key holding a non-object value (e.g. a string or number) supplied by earlier code or a caller-provided body.","triggerScenarios":"Calling `merge_google_options` (directly or via `build_request_body` / `merge_google_places_thinking_config_under_generation_config`) on a `body_obj` where `\"generationConfig\"` was pre-populated with a non-object JSON value instead of an object.","commonSituations":"Hand-constructed or cached request bodies where generationConfig was accidentally serialized as a JSON string; a provider-specific fixup writing a scalar under generationConfig before options merging runs; refactors that changed generationConfig's shape.","solutions":["Inspect the request body just before the merge and log `body_obj[\"generationConfig\"]` to see what non-object value is present.","Normalize the entry before merging: if `generationConfig` exists but is not an object, replace it with an empty object (or return a descriptive error).","Ensure all writers of generationConfig insert `Value::Object(Map::new())`, never scalars or arrays.","If callers may pass arbitrary bodies, change `merge_google_options` to return `Result` and reject non-object generationConfig explicitly."],"exampleFix":"// before\nlet generation_config_obj = generation_config\n    .as_object_mut()\n    .expect(\"generationConfig must be an object\");\n// after\nlet generation_config_obj = match generation_config {\n    Value::Object(map) => map,\n    other => {\n        *other = Value::Object(Map::new());\n        other.as_object_mut().unwrap()\n    }\n};","handlingStrategy":"validation","validationCode":"// Before merging options, validate the body's generationConfig shape:\nfn generation_config_is_object(body: &serde_json::Value) -> bool {\n    body.get(\"generationConfig\")\n        .map(|v| v.is_object() || v.is_null())\n        .unwrap_or(true)\n}","typeGuard":"fn as_object_mut_or_default(v: &mut serde_json::Value) -> &mut serde_json::Map<String, serde_json::Value> {\n    if !v.is_object() { *v = serde_json::Value::Object(serde_json::Map::new()); }\n    v.as_object_mut().expect(\"just replaced with object\")\n}","tryCatchPattern":"// Panic, not Result — guard the call site:\nstd::panic::catch_unwind(std::panic::AssertUnwindSafe(|| merge_google_options(&mut body, options)))\n    .map_err(|_| anyhow::anyhow!(\"generationConfig merge failed: body had non-object generationConfig\"))?","preventionTips":["Always construct generationConfig as Value::Object(Map::new())","Never deserialize cached bodies into request paths without schema validation","Add a unit test merging options into a body that already has generationConfig","Log the full body before option merging in debug builds"],"tags":["rust","serde-json","google-provider","invariant","panic"],"backgroundTag":"internal-invariant-violation","analyzedSha":"b0637c97ec34144387cbf2f74f65df6d16a6cef1","analyzedAt":"2026-09-10T00:24:58.650Z","contentChangedAt":"2026-09-10T00:24:58.650Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}