Hmbown/CodeWhale · error

config entry must be a string

Error message

config entry {dotted:?} must be a string

What it means

prepare_import processes each config entry from the bundle; the special key 'provider' (the selected provider selector) must be a TOML string because it names a provider identity that is restored into route preferences. If the bundle contains provider as a non-string TOML value (integer, table, boolean), the import refuses with this message rather than guessing.

Solutions

  1. Edit the bundle TOML so provider is a quoted string: provider = "openai".
  2. Remove the provider key entirely if no provider should be selected.
  3. Regenerate the bundle from a working export instead of hand-authoring it.
  4. Validate the bundle's provider entry type before importing.

Example fix

// before (bundle body)
provider = 42
// after (bundle body)
provider = "openai"
Defensive patterns

Strategy: validation

Validate before calling

let doc: toml::Value = toml::from_str(&bundle_body)?; if let Some(p) = doc.get("provider") { if !p.is_str() { bail!("provider must be a string in bundle"); } }

Type guard

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

Try / catch

if let Err(e) = prepare_import(...) { if e.to_string().contains("must be a string") { eprintln!("fix provider value type in bundle TOML"); } }

Prevention

When it happens

Trigger: Importing a bundle whose TOML body sets provider = 123, provider = true, or provider = { ... } instead of a string, during run_import / apply_bundle / dry-run planning.

Common situations: Hand-edited or machine-generated bundle files with a mistyped provider value; schema drift between bundle producers and consumers.

Related errors


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

Appendix: source

Thrown at crates/cli/src/config_bundles.rs:1194

        ("plugins", &bundle.plugins),
        ("project", &bundle.project),
        ("global", &bundle.global),
    ] {
        if !section_applies(section, scope) {
            continue;
        }
        for (key, value) in &table.entries {
            let dotted = format!("{section}.{key}");
            if nonportable_path_reason(key).is_some()
                || value_rejection_reason(key, value).is_some()
            {
                bail!("refusing to import non-portable config path {dotted}");
            }
            if key == "provider" {
                selected_provider = Some(
                    value
                        .as_str()
                        .ok_or_else(|| anyhow!("config entry {dotted:?} must be a string"))?,
                );
                continue;
            }
            collect_model_edits(&dotted, key, value, &mut model_edits)?;
            if codewhale_tui::route_preferences::is_route_key(key) {
                continue;
            }
            apply_config_value(&mut candidate, key, value)?;
        }
    }
    document = toml::to_string(&toml::Value::Table(config_document(&candidate)?))?
        .parse()
        .map_err(|_| anyhow!("could not prepare imported configuration; contents omitted"))?;
    // Definitions precede the exact final selector. Root aliases then target
    // that selected route, so an old provider slot cannot mask an imported model.
    if let Some(provider) = selected_provider {
        codewhale_tui::route_preferences::set_document(
            store.path(),

View on GitHub (pinned to 73e0f67d83)