BigPizzaV3/CodexPlusPlus · error · anyhow::Error

{key} must be a TOML table

Error message

{key} must be a TOML table

What it means

table_mut_or_insert (crates/codex-plus-core/src/computer_use_guard.rs:758) ensures a TOML key exists as a table: it inserts toml_edit::table() when the key is absent, overwrites with a fresh table when the existing item is not a table, and then fetches it mutably. The final 'must be a TOML table' error is only reachable if that forced reassignment still yields a non-table — effectively a defensive invariant covering pathological toml_edit document states (e.g. dotted-key indexing quirks or corrupted DocumentMut).

Source

Thrown at crates/codex-plus-core/src/computer_use_guard.rs:758

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

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_plugin_enabled(doc: &mut DocumentMut, plugin_id: &str) -> anyhow::Result<()> {
    let plugins = table_mut_or_insert(doc, "plugins")?;
    if !plugins.contains_key(plugin_id) {
        plugins[plugin_id] = toml_edit::table();
    }
    if plugins.get(plugin_id).and_then(Item::as_table).is_none() {
        plugins[plugin_id] = toml_edit::table();
    }
    plugins[plugin_id]["enabled"] = toml_edit::value(true);
    Ok(())
}

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

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. Log the key and dump the DocumentMut at failure — a key containing '.' or unusual indexing is the prime suspect
  2. Check the caller (ensure_plugin_enabled passes 'plugins') has not started passing composite/dotted keys
  3. Round-trip the file through from_str again to rule out a corrupted in-memory document
  4. Treat any stock-config reproduction as a bug in the guard, not a user config problem
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check the TOML shape before mutating
let doc: toml_edit::DocumentMut = text.parse()?;
ensure!(doc.get(key).map_or(true, |item| item.is_table()),
    "{key} exists but is not a table — fix config.toml manually");
let table = table_mut_or_insert(&mut doc, key)?;

Type guard

fn toml_key_is_table_or_absent(doc: &toml_edit::DocumentMut, key: &str) -> bool {
    match doc.get(key) { None => true, Some(item) => item.is_table() }
}

Try / catch

match table_mut_or_insert(&mut doc, key) {
    Ok(t) => Ok(t),
    Err(e) if e.to_string().contains("must be a TOML table") => {
        // defensive branch tripped — rebuild the document from scratch rather than trusting it
        let mut fresh = toml_edit::DocumentMut::new();
        fresh[key] = toml_edit::table();
        // …re-apply intended edits to `fresh`…
        Ok(fresh[key].as_table_mut().unwrap())
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Editing a config.toml whose 'plugins' (or the passed key) entry resists table coercion after the two insert/overwrite attempts — not reproducible with ordinary TOML content since the function unconditionally rewrites the item to a table first.

Common situations: Effectively unreachable via user config; would require a code path passing a dotted key string or a DocumentMut built abnormally. If it fires, suspect a code regression in how the key/index is passed, not the file on disk.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16). Data as JSON: /api/errors/4503b7c4563e5e30. Report an issue: GitHub.