Hmbown/CodeWhale · error

Cannot export a non-string default_model without losing its…

Error message

Cannot export a non-string default_model without losing its value

What it means

When export scrubbing finds no canonical model identity (None), it must still remove default_model; but if the document's default_model is not a plain string, removing it would silently discard a value the tool cannot interpret, so it refuses via ensure!. This prevents non-string (e.g. table or array) default_model values from being dropped during export.

Solutions

  1. Convert default_model to a plain string model name before exporting
  2. Remove the malformed default_model manually after backing up its value
  3. Regenerate the config from defaults and reapply the intended model string

Example fix

# before
default_model = { name = "deepseek-chat" }
# after
default_model = "deepseek-chat"
Defensive patterns

Strategy: validation

Validate before calling

let ok = doc.get("default_model").map_or(true, |v| v.as_str().is_some());
anyhow::ensure!(ok, "default_model must be a string before export");

Type guard

fn default_model_is_string(doc: &toml::Table) -> bool {
    doc.get("default_model").map_or(true, toml::Value::is_str)
}

Prevention

When it happens

Trigger: Exporting a document with no resolvable model identity while default_model exists in the document as a non-string TOML value (table, integer, array, etc.).

Common situations: Corrupted or hand-edited configs where default_model was written as a table; tooling that wrote unexpected types into the root key.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/27cd3a6b508e9303. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/route_preferences.rs:250

            // A root alias the DeepSeek route still consumes lands in this
            // same slot on import; the live choice wins over the dead alias.
            let root_covers_slot = active_slot == deepseek_slot
                && ["model", "default_text_model"]
                    .iter()
                    .any(|key| document.get(*key).and_then(toml::Value::as_str).is_some());
            let folded = !value.is_empty()
                && !root_covers_slot
                && document_slot_value(document, &deepseek_slot).is_none();
            if folded {
                ensure!(
                    insert_document_slot_value(document, &deepseek_slot, &value),
                    "Cannot export default_model into a non-table provider slot"
                );
            }
            document.remove("default_model");
        }
        None => {
            ensure!(
                !document.contains_key("default_model"),
                "Cannot export a non-string default_model without losing its value"
            );
        }
    }
    Ok(())
}

fn project_root_key<'a>(path: &Path, key: &'a str) -> Option<&'a str> {
    (codewhale_config::config_path_is_workspace_scoped(path)
        && matches!(key, "model" | "default_text_model"))
    .then_some(key)
}

fn saved_config(store: &codewhale_config::ConfigStore) -> Result<Config> {
    let rendered;
    let body = if let Some(original) = store.original_body() {
        original

View on GitHub (pinned to 73e0f67d83)