Hmbown/CodeWhale · error

` ` in config.toml must be a table

Error message

`{segment}` in config.toml must be a table

What it means

Thrown by table_like_at_path_mut when walking a dotted path inside config.toml: an intermediate segment resolves to a non-table value (e.g. a string or integer), but the operation needs a table to descend into or create. For Create lookups the library refuses to overwrite the existing scalar; for Existing lookups it reports the path as absent (Ok(None)) via the caller. It protects the TOML document from being silently reshaped.

Solutions

  1. Open config.toml and convert the offending key into a section: change `provider = "x"` to `[provider]` with the child keys under it
  2. Use a path whose intermediate segments are actual TOML tables
  3. Remove the scalar key if it is obsolete, then set the nested value
  4. Back up and regenerate the config section with the tooling's default writer

Example fix

// before (config.toml)
model = "glm"
model.temperature = 0.5
// after (config.toml)
[model]
name = "glm"
temperature = 0.5
Defensive patterns

Strategy: validation

Validate before calling

// Check each intermediate segment is a TOML table before setting
fn segments_ok(doc: &toml::Value, path: &str) -> bool {
    let segs: Vec<&str> = path.split('.').collect();
    for s in &segs[..segs.len()-1] {
        match doc.get(*s) {
            Some(v) if v.is_table() => {}
            _ => return false,
        }
    }
    true
}

Prevention

When it happens

Trigger: set_config_document_value with a path like "provider.name" where `provider` is currently a string/number/bool in config.toml; unset_config_document_value walking through the same conflict; editing config.toml by hand so a key that used to be a section [provider] becomes a scalar `provider = "x"`.

Common situations: Merging snippets into config.toml where a section header is accidentally converted to a key-value pair; older config versions with flat keys being addressed with nested paths.

Related errors


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

Appendix: source

Thrown at crates/config/src/config_document.rs:449

    let mut current: &mut dyn toml_edit::TableLike = root;
    for segment in segments {
        if current.get(segment).is_none() {
            match lookup {
                PathLookup::Create => {
                    let mut table = toml_edit::Table::new();
                    table.set_implicit(true);
                    current.insert(segment, toml_edit::Item::Table(table));
                }
                PathLookup::Existing => return Ok(None),
            }
        }
        let item = current
            .get_mut(segment)
            .expect("segment exists or was inserted above");
        match item.as_table_like_mut() {
            Some(table) => current = table,
            None => match lookup {
                PathLookup::Create => bail!("`{segment}` in config.toml must be a table"),
                PathLookup::Existing => return Ok(None),
            },
        }
    }
    Ok(Some(current))
}

#[cfg(test)]
mod tests {
    #[test]
    fn healing_keeps_a_non_table_extras_key_it_cannot_lift() {
        // `extras` is where the config structs flatten unknown keys, so a
        // scalar or array under that exact name round-trips through the typed
        // path as ordinary user data. Healing used to `remove()` it before
        // discovering it was not a table, dropping it on the very next
        // `codewhale config set` — and reporting `healed == false` while doing
        // so.
        for body in [

View on GitHub (pinned to 73e0f67d83)