{"record":{"id":"781fe91793979c58","repo":"BigPizzaV3/CodexPlusPlus","slug":"table-name-toml","errorCode":null,"errorMessage":"{table_name} 必须是 TOML 表","messagePattern":"(.+?) 必须是 TOML 表","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/relay_config.rs","lineNumber":884,"sourceCode":"pub fn upsert_context_entry_in_common_config(\n    common_config: &str,\n    kind: &str,\n    id: &str,\n    toml_body: &str,\n) -> anyhow::Result<String> {\n    let id = id.trim();\n    if id.is_empty() {\n        anyhow::bail!(\"上下文 id 不能为空\");\n    }\n    let table_name = context_table_name(kind)?;\n    let body_doc = parse_toml_document(toml_body)?;\n    let normalized = normalize_duplicate_toml_text(common_config);\n    let mut doc = parse_toml_document(&normalized)?;\n    if !doc.as_table().contains_key(table_name) {\n        doc[table_name] = toml_edit::table();\n    }\n    if doc[table_name].as_table().is_none() {\n        anyhow::bail!(\"{table_name} 必须是 TOML 表\");\n    }\n    doc[table_name][id] = Item::Table(body_doc.as_table().clone());\n    Ok(normalize_optional_toml(doc))\n}\n\npub fn delete_context_entry_from_common_config(\n    common_config: &str,\n    kind: &str,\n    id: &str,\n) -> anyhow::Result<String> {\n    let table_name = context_table_name(kind)?;\n    let normalized = normalize_duplicate_toml_text(common_config);\n    let mut doc = parse_toml_document(&normalized)?;\n    if let Some(table) = doc[table_name].as_table_mut() {\n        table.remove(id.trim());\n        if table.is_empty() {\n            doc.as_table_mut().remove(table_name);\n        }","sourceCodeStart":866,"sourceCodeEnd":902,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/relay_config.rs#L866-L902","documentation":"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 映射而来）。","triggerScenarios":"common config 中已存在 mcp_servers = \"...\"（字符串）、mcp_servers = [...]（数组）或其它非 table 形态的值时调用 upsert。注意：键不存在时函数会自动建表，所以只有「已存在的同名键值不是表」才触发。","commonSituations":"手写公共配置时把 mcp_servers 写成字符串或数组；其他工具写入的 common config 格式不兼容；把条目数组当成表结构粘贴。","solutions":["把 common config 中的 mcp_servers/skills/plugins 改为 [mcp_servers] 表（或删除该键让函数自动建表）","调用前用 toml 解析检查该键的值类型是否为 table","若来自外部工具，先 sanitize_common_config_contents 清理后再操作"],"exampleFix":"# before (common config)\nmcp_servers = \"fetch,filesystem\"\n\n# after\n[mcp_servers.fetch]\ncommand = \"npx\"\nargs = [\"-y\", \"@modelcontextprotocol/server-fetch\"]","handlingStrategy":"type-guard","validationCode":"let doc: toml_edit::DocumentMut = common_config.parse()?;\nif let Some(item) = doc.get(\"mcp_servers\") {\n    anyhow::ensure!(item.is_table(), \"mcp_servers 必须是 TOML 表\");\n}\nupsert_context_entry_in_common_config(common_config, kind, id, body)?;","typeGuard":"fn context_table_is_table(common: &str, table: &str) -> bool {\n    common\n        .parse::<toml_edit::DocumentMut>()\n        .ok()\n        .and_then(|doc| doc.get(table).cloned())\n        .map(|item| item.is_table() || item.as_table().is_some())\n        .unwrap_or(true) // 键不存在时 upsert 会自动建表\n}","tryCatchPattern":"if let Err(err) = upsert_context_entry_in_common_config(&common, kind, id, body) {\n    if err.to_string().contains(\"必须是 TOML 表\") {\n        // 定位损坏的表键（mcp_servers/skills/plugins），提示用户修正结构\n    } else { return Err(err); }\n}","preventionTips":["手写 common config 时用 [table.header] 语法而非字符串/数组","外部来源的 common config 先 sanitize 再操作","保存前用 TOML 解析 + 结构断言做编辑器实时校验"],"tags":["common-config","toml","schema","codex-plus-core"],"backgroundTag":"toml-schema-mismatch","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}