BigPizzaV3/CodexPlusPlus · error · anyhow::Error

model_providers.{provider_id} 必须是 TOML table

Error message

model_providers.{provider_id} 必须是 TOML table

What it means

ensure_provider_table 保证 config.toml 中 model_providers.{provider_id} 存在且是 table,供后续写入 base_url / experimental_bearer_token 等字段。函数在键缺失或值不是 table 时会先插入新表,最后才用 as_table_mut 取可变引用——按构造逻辑该 bail 在当前实现中是防御性不变量,正常不可达。若真出现,说明 model_providers 段结构损坏或 provider_id 异常导致 toml_edit 键操作行为异常。

Source

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

}

fn ensure_provider_table<'a>(
    doc: &'a mut DocumentMut,
    provider_id: &str,
) -> anyhow::Result<&'a mut Table> {
    let providers = table_mut_or_insert(doc, "model_providers")?;
    if !providers.contains_key(provider_id)
        || providers
            .get(provider_id)
            .and_then(Item::as_table)
            .is_none()
    {
        providers.insert(provider_id, toml_edit::table());
    }
    providers
        .get_mut(provider_id)
        .and_then(Item::as_table_mut)
        .ok_or_else(|| anyhow::anyhow!("model_providers.{provider_id} 必须是 TOML table"))
}

fn remove_provider_table(doc: &mut DocumentMut, provider_id: &str) {
    if let Some(providers) = doc.get_mut("model_providers").and_then(Item::as_table_mut) {
        providers.remove(provider_id);
        if providers.is_empty() {
            doc.as_table_mut().remove("model_providers");
        }
    }
}

fn retain_only_provider_table(doc: &mut DocumentMut, provider_id: &str) {
    if let Some(providers) = doc.get_mut("model_providers").and_then(Item::as_table_mut) {
        let provider = providers
            .remove(provider_id)
            .unwrap_or_else(toml_edit::table);
        providers.clear();
        providers.insert(provider_id, provider);

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. 打开 config.toml 检查 [model_providers] 段语法与结构,修复或删除损坏的 provider 条目
  2. 检查 provider_id 是否为常规标识符(避免点号/空串等异常值)
  3. 备份后让 CodexPlusPlus 重新应用 profile,重建 provider 表

Example fix

# before (config.toml, 损坏形态)
model_providers.foo = 1

# after
[model_providers.foo]
name = "foo"
base_url = "https://api.example.com/v1"
Defensive patterns

Strategy: validation

Validate before calling

anyhow::ensure!(!provider_id.trim().is_empty(), "provider_id 不能为空");
let doc: toml_edit::DocumentMut = config_contents.parse()?;
if let Some(item) = doc.get("model_providers") {
    anyhow::ensure!(item.is_table() || item.as_table().is_some(), "model_providers 必须是 TOML table");
}
// 之后再调用会走 ensure_provider_table 的写入流程

Try / catch

if let Err(err) = apply_relay_profile_to_home_with_switch_rules_and_computer_use_guard(&home, &profile, &common, guard) {
    if err.to_string().contains("必须是 TOML table") {
        // 防御性不变量被打破:备份 config.toml,检查并重建 [model_providers] 段后重试
    } else { return Err(err); }
}

Prevention

When it happens

Trigger: 写入/更新 provider(如 set_experimental_bearer_token_in_config、upsert_model_provider_config 内部)时,model_providers 表内部结构异常(例如被并发写入破坏、inline table / dotted-key 混用导致 as_table_mut 失败)。防御分支,正常构造路径下插入后必为 table。

Common situations: config.toml 被外部进程并发写坏;provider_id 含特殊字符(点号等)造成 toml_edit 键语义歧义;使用非常规工具编辑过 model_providers 段。

Related errors


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