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

  1. Inspect the request body just before the merge and log `body_obj["generationConfig"]` to see what non-object value is present.
  2. Normalize the entry before merging: if `generationConfig` exists but is not an object, replace it with an empty object (or return a descriptive error).
  3. Ensure all writers of generationConfig insert `Value::Object(Map::new())`, never scalars or arrays.
  4. 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

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


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)