BigPizzaV3/CodexPlusPlus · error

必须是 TOML 表

Error message

{table_name} 必须是 TOML 表

What it means

After ensuring the target table key exists in the normalized common config, the function checks that the value at table_name is actually a TOML table. If an existing key of that name holds a scalar or array, inserting a sub-table under it is impossible, so the library bails naming the offending table.

Solutions

  1. Edit config.toml so the key (e.g. [mcp_servers]) is a proper table, removing the scalar/array value
  2. Back up and delete the conflicting scalar key, then retry the upsert
  3. Use delete_context_entry_from_common_config / normalization to clean the malformed section first

Example fix

// before (config.toml)
mcp_servers = 3
// after (config.toml)
[mcp_servers.my-server]
command = "foo"
Defensive patterns

Strategy: validation

Validate before calling

let doc: toml_edit::DocumentMut = config.parse()?;
let table_name = context_table_name(kind)?;
if let Some(item) = doc.get(table_name) {
    if item.as_table().is_none() {
        return Err(anyhow::anyhow!("{table_name} 必须是 TOML 表"));
    }
}

Try / catch

match upsert_context_entry_in_common_config(kind, id, body, config) {
    Err(e) if e.to_string().ends_with("必须是 TOML 表") => repair_or_rename_section(kind)?,
    other => other?,
}

Prevention

When it happens

Trigger: Calling upsert_context_entry_in_common_config when the common config already contains e.g. model_context_window = 5 or context = ["a"] — a non-table value under the table name returned by context_table_name(kind).

Common situations: Hand-edited config.toml where a context section name was overwritten with a plain value; a merge tool flattened a table into a scalar; migrating an older config format into the new schema.

Related errors


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

Appendix: source

Thrown at crates/codex-plus-core/src/relay_config.rs:1049

pub fn upsert_context_entry_in_common_config(
    common_config: &str,
    kind: &str,
    id: &str,
    toml_body: &str,
) -> anyhow::Result<String> {
    let id = id.trim();
    if id.is_empty() {
        anyhow::bail!("上下文 id 不能为空");
    }
    let table_name = context_table_name(kind)?;
    let body_doc = parse_toml_document(toml_body)?;
    let normalized = normalize_duplicate_toml_text(common_config);
    let mut doc = parse_toml_document(&normalized)?;
    if !doc.as_table().contains_key(table_name) {
        doc[table_name] = toml_edit::table();
    }
    if doc[table_name].as_table().is_none() {
        anyhow::bail!("{table_name} 必须是 TOML 表");
    }
    doc[table_name][id] = Item::Table(body_doc.as_table().clone());
    Ok(normalize_optional_toml(doc))
}

pub fn delete_context_entry_from_common_config(
    common_config: &str,
    kind: &str,
    id: &str,
) -> anyhow::Result<String> {
    let table_name = context_table_name(kind)?;
    let normalized = normalize_duplicate_toml_text(common_config);
    let mut doc = parse_toml_document(&normalized)?;
    if let Some(table) = doc[table_name].as_table_mut() {
        table.remove(id.trim());
        if table.is_empty() {
            doc.as_table_mut().remove(table_name);
        }

View on GitHub (pinned to b1ed92e5e4)