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
- 把 common config 中的 mcp_servers/skills/plugins 改为 [mcp_servers] 表(或删除该键让函数自动建表)
- 调用前用 toml 解析检查该键的值类型是否为 table
- 若来自外部工具,先 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
- 手写 common config 时用 [table.header] 语法而非字符串/数组
- 外部来源的 common config 先 sanitize 再操作
- 保存前用 TOML 解析 + 结构断言做编辑器实时校验
When it happens
Trigger: common config 中已存在 mcp_servers = "..."(字符串)、mcp_servers = [...](数组)或其它非 table 形态的值时调用 upsert。注意:键不存在时函数会自动建表,所以只有「已存在的同名键值不是表」才触发。
Common situations: 手写公共配置时把 mcp_servers 写成字符串或数组;其他工具写入的 common config 格式不兼容;把条目数组当成表结构粘贴。
Related errors
- model_providers.{provider_id} 必须是 TOML table
- {} must be a JSON object
- {key} must be a TOML table
- config.toml TOML parse failed: {error}
- 中转 Key 不能为空
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16).
Data as JSON: /api/errors/781fe91793979c58.
Report an issue: GitHub.