Kuberwastaken/claurst · error
generationConfig must be an object
Error message
generationConfig must be an object
What it means
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.
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.
Example fix
// before
let generation_config_obj = generation_config
.as_object_mut()
.expect("generationConfig must be an object");
// after
let generation_config_obj = match generation_config {
Value::Object(map) => map,
other => {
*other = Value::Object(Map::new());
other.as_object_mut().unwrap()
}
}; Defensive patterns
Strategy: validation
Validate before calling
// Before merging options, validate the body's generationConfig shape:
fn generation_config_is_object(body: &serde_json::Value) -> bool {
body.get("generationConfig")
.map(|v| v.is_object() || v.is_null())
.unwrap_or(true)
} Type guard
fn as_object_mut_or_default(v: &mut serde_json::Value) -> &mut serde_json::Map<String, serde_json::Value> {
if !v.is_object() { *v = serde_json::Value::Object(serde_json::Map::new()); }
v.as_object_mut().expect("just replaced with object")
} Try / catch
// Panic, not Result — guard the call site:
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| merge_google_options(&mut body, options)))
.map_err(|_| anyhow::anyhow!("generationConfig merge failed: body had non-object generationConfig"))? Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- failed to build reqwest client
- Failed to build reqwest client
- stdin was requested as piped
- (?:https?|ftp)://\S+|www\.\S+
- valid OAuth authorize base URL
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/9256a7f88eccc2d2.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/api/src/providers/request_options.rs:74
"temperature",
"thinkingConfig",
"topK",
"topP",
];
let Some(body_obj) = body.as_object_mut() else {
return;
};
let Some(options_obj) = provider_options.as_object() else {
return;
};
let generation_config = body_obj
.entry("generationConfig".to_string())
.or_insert_with(|| Value::Object(Map::new()));
let generation_config_obj = generation_config
.as_object_mut()
.expect("generationConfig must be an object");
let mut root_entries: Vec<(String, Value)> = Vec::new();
for (key, value) in options_obj {
if GENERATION_CONFIG_KEYS.contains(&key.as_str()) {
generation_config_obj.insert(key.clone(), value.clone());
} else {
root_entries.push((key.clone(), value.clone()));
}
}
for (key, value) in root_entries {
body_obj.insert(key, value);
}
}
pub(crate) fn merge_bedrock_options(body: &mut Value, provider_options: &Value) {
let Some(body_obj) = body.as_object_mut() else {
return;
View on GitHub (pinned to b0637c97ec)