BigPizzaV3/CodexPlusPlus · error · anyhow::Error

{table_name} 必须是 TOML 表

Error message

{table_name} 必须是 TOML 表

What it means

upsert_context_entry_in_common_config 在写入条目前确保目标表存在:若 common config 中没有该表则插入空表;若键已存在但值不是 TOML 表(Item::as_table 为 None,例如标量、数组或 inline table 的某些形态)则 bail「{table_name} 必须是 TOML 表」。table_name 是 mcp_servers / skills / plugins 之一(由 kind 映射而来)。

Source

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

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 1f431ae49b)

Solutions

  1. 把 common config 中的 mcp_servers/skills/plugins 改为 [mcp_servers] 表(或删除该键让函数自动建表)
  2. 调用前用 toml 解析检查该键的值类型是否为 table
  3. 若来自外部工具,先 sanitize_common_config_contents 清理后再操作

Example fix

# before (common config)
mcp_servers = "fetch,filesystem"

# after
[mcp_servers.fetch]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-fetch"]
Defensive patterns

Strategy: type-guard

Validate before calling

let doc: toml_edit::DocumentMut = common_config.parse()?;
if let Some(item) = doc.get("mcp_servers") {
    anyhow::ensure!(item.is_table(), "mcp_servers 必须是 TOML 表");
}
upsert_context_entry_in_common_config(common_config, kind, id, body)?;

Type guard

fn context_table_is_table(common: &str, table: &str) -> bool {
    common
        .parse::<toml_edit::DocumentMut>()
        .ok()
        .and_then(|doc| doc.get(table).cloned())
        .map(|item| item.is_table() || item.as_table().is_some())
        .unwrap_or(true) // 键不存在时 upsert 会自动建表
}

Try / catch

if let Err(err) = upsert_context_entry_in_common_config(&common, kind, id, body) {
    if err.to_string().contains("必须是 TOML 表") {
        // 定位损坏的表键(mcp_servers/skills/plugins),提示用户修正结构
    } else { return Err(err); }
}

Prevention

When it happens

Trigger: common config 中已存在 mcp_servers = "..."(字符串)、mcp_servers = [...](数组)或其它非 table 形态的值时调用 upsert。注意:键不存在时函数会自动建表,所以只有「已存在的同名键值不是表」才触发。

Common situations: 手写公共配置时把 mcp_servers 写成字符串或数组;其他工具写入的 common config 格式不兼容;把条目数组当成表结构粘贴。

Related errors


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