BigPizzaV3/CodexPlusPlus · error

must be a TOML table

Error message

{key} must be a TOML table

What it means

table_mut_or_insert ensures a top-level key in a config.toml DocumentMut is a table, replacing non-conflicting values with a new table, and returns a mutable reference. If the key exists as an array or other non-table item that cannot be coerced (as_table_mut fails after the insert attempt), this error is thrown rather than overwriting user data blindly.

Solutions

  1. Locate the key in config.toml and convert it to a proper table: change `key = value` to `[key]` with sub-keys.
  2. Rename the conflicting key if it was intentional user data, then re-run the merge.
  3. Back up config.toml before merges so a type conflict can be recovered.
  4. Add a pre-merge validation pass that rejects configs with non-table values for managed keys, with a clear message.

Example fix

// before: type conflict
plugins = true
// after: table form expected by merge
[plugins]
enabled = true
Defensive patterns

Strategy: validation

Validate before calling

if let Some(v) = doc.get(key) {
    if v.as_table().is_none() {
        eprintln!("config key '{key}' must be a [table], found: {v}");
    }
}

Type guard

fn is_table(doc: &toml_edit::DocumentMut, key: &str) -> bool {
    doc.get(key).map_or(true, |item| item.as_table().is_some())
}

Try / catch

match merge_marketplace_configs_and_plugins_into_text(&config) {
    Ok(merged) => {},
    Err(e) if e.to_string().ends_with("must be a TOML table") => {
        eprintln!("{e}; fix the offending key in config.toml to use [section] syntax");
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: merge_marketplace_configs_and_plugins_into_text calls table_mut_or_insert for a key (e.g. a marketplace or plugin section name) that already exists in config.toml as a non-table TOML value (string, integer, array of tables handled oddly, etc.).

Common situations: User declared something like plugins = "enabled" instead of [plugins]; a list value conflicts with the expected table; an upstream config writer emitted arrays where tables were expected.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/58915ccf8846e846. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-core/src/plugin_marketplace.rs:859

    if contents.trim().is_empty() {
        Ok(DocumentMut::new())
    } else {
        contents
            .parse::<DocumentMut>()
            .map_err(|error| anyhow::anyhow!("config.toml TOML parse failed: {error}"))
    }
}

fn table_mut_or_insert<'a>(doc: &'a mut DocumentMut, key: &str) -> anyhow::Result<&'a mut Table> {
    if !doc.as_table().contains_key(key) {
        doc[key] = toml_edit::table();
    }
    if doc.get(key).and_then(Item::as_table).is_none() {
        doc[key] = toml_edit::table();
    }
    doc.get_mut(key)
        .and_then(Item::as_table_mut)
        .ok_or_else(|| anyhow::anyhow!("{key} must be a TOML table"))
}

fn ensure_trailing_newline(mut contents: String) -> String {
    if !contents.ends_with('\n') {
        contents.push('\n');
    }
    contents
}

#[cfg(test)]
mod tests {
    use super::*;

    fn expected_marketplace_path(path: &Path) -> String {
        if cfg!(windows) {
            format!(r"\\?\{}", path.display())
        } else {
            path.to_string_lossy().into_owned()

View on GitHub (pinned to b1ed92e5e4)