{"record":{"id":"95d5b034e60e32eb","repo":"BigPizzaV3/CodexPlusPlus","slug":"model-providers-provider-id-toml-table","errorCode":null,"errorMessage":"model_providers.{provider_id} 必须是 TOML table","messagePattern":"model_providers\\.(.+?) 必须是 TOML table","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/relay_config.rs","lineNumber":2579,"sourceCode":"}\n\nfn ensure_provider_table<'a>(\n    doc: &'a mut DocumentMut,\n    provider_id: &str,\n) -> anyhow::Result<&'a mut Table> {\n    let providers = table_mut_or_insert(doc, \"model_providers\")?;\n    if !providers.contains_key(provider_id)\n        || providers\n            .get(provider_id)\n            .and_then(Item::as_table)\n            .is_none()\n    {\n        providers.insert(provider_id, toml_edit::table());\n    }\n    providers\n        .get_mut(provider_id)\n        .and_then(Item::as_table_mut)\n        .ok_or_else(|| anyhow::anyhow!(\"model_providers.{provider_id} 必须是 TOML table\"))\n}\n\nfn remove_provider_table(doc: &mut DocumentMut, provider_id: &str) {\n    if let Some(providers) = doc.get_mut(\"model_providers\").and_then(Item::as_table_mut) {\n        providers.remove(provider_id);\n        if providers.is_empty() {\n            doc.as_table_mut().remove(\"model_providers\");\n        }\n    }\n}\n\nfn retain_only_provider_table(doc: &mut DocumentMut, provider_id: &str) {\n    if let Some(providers) = doc.get_mut(\"model_providers\").and_then(Item::as_table_mut) {\n        let provider = providers\n            .remove(provider_id)\n            .unwrap_or_else(toml_edit::table);\n        providers.clear();\n        providers.insert(provider_id, provider);","sourceCodeStart":2561,"sourceCodeEnd":2597,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/relay_config.rs#L2561-L2597","documentation":"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 键操作行为异常。","triggerScenarios":"写入/更新 provider（如 set_experimental_bearer_token_in_config、upsert_model_provider_config 内部）时，model_providers 表内部结构异常（例如被并发写入破坏、inline table / dotted-key 混用导致 as_table_mut 失败）。防御分支，正常构造路径下插入后必为 table。","commonSituations":"config.toml 被外部进程并发写坏；provider_id 含特殊字符（点号等）造成 toml_edit 键语义歧义；使用非常规工具编辑过 model_providers 段。","solutions":["打开 config.toml 检查 [model_providers] 段语法与结构，修复或删除损坏的 provider 条目","检查 provider_id 是否为常规标识符（避免点号/空串等异常值）","备份后让 CodexPlusPlus 重新应用 profile，重建 provider 表"],"exampleFix":"# before (config.toml, 损坏形态)\nmodel_providers.foo = 1\n\n# after\n[model_providers.foo]\nname = \"foo\"\nbase_url = \"https://api.example.com/v1\"","handlingStrategy":"validation","validationCode":"anyhow::ensure!(!provider_id.trim().is_empty(), \"provider_id 不能为空\");\nlet doc: toml_edit::DocumentMut = config_contents.parse()?;\nif let Some(item) = doc.get(\"model_providers\") {\n    anyhow::ensure!(item.is_table() || item.as_table().is_some(), \"model_providers 必须是 TOML table\");\n}\n// 之后再调用会走 ensure_provider_table 的写入流程","typeGuard":null,"tryCatchPattern":"if let Err(err) = apply_relay_profile_to_home_with_switch_rules_and_computer_use_guard(&home, &profile, &common, guard) {\n    if err.to_string().contains(\"必须是 TOML table\") {\n        // 防御性不变量被打破：备份 config.toml，检查并重建 [model_providers] 段后重试\n    } else { return Err(err); }\n}","preventionTips":["provider_id 使用常规标识符（字母数字连字符），避免点号/空白","避免外部进程并发写 config.toml","出现时先备份再让工具重建 provider 表"],"tags":["toml","model-providers","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"}