BigPizzaV3/CodexPlusPlus · error
model_providers. 必须是 TOML table
Error message
model_providers.{provider_id} 必须是 TOML table What it means
The provider-table helper inserts model_providers.<provider_id> if absent, then requires the entry to be a mutable TOML table. If the entry exists but holds a non-table value (string, integer, array), as_table_mut returns None and it throws "model_providers.{provider_id} 必须是 TOML table". Unlike error 461 (read-only patch), this one tries to create the provider first but cannot overwrite an existing wrong-typed value.
Solutions
- Replace the scalar entry for the named provider with a proper table: [model_providers.<provider_id>] with key/value fields.
- If using [[model_providers]] array-of-tables syntax, rewrite as [model_providers.<id>] named tables.
- Delete the bad entry and re-apply the profile so the helper inserts a fresh empty table.
- Validate config.toml structure (each model_providers child must be a table) before running apply.
Example fix
// before (config.toml) model_providers.openai = "https://api.openai.com/v1" // after (config.toml) [model_providers.openai] base_url = "https://api.openai.com/v1"
Defensive patterns
Strategy: validation
Validate before calling
fn provider_entry_is_table(doc: &toml_edit::DocumentMut, id: &str) -> bool {
doc.get("model_providers")
.and_then(|p| p.get(id))
.map(|i| i.is_table())
.unwrap_or(true) // absent is fine: helper will insert it
} Type guard
fn as_provider_table(item: Option<&toml_edit::Item>) -> Option<&toml_edit::Table> {
item.and_then(|i| i.as_table())
} Try / catch
match apply_relay_profile_to_home(...) {
Err(e) if e.to_string().contains("必须是 TOML table") => {
// remove the scalar model_providers.<id> entry, re-apply to recreate it as a table
}
other => other?,
} Prevention
- Always declare providers as [model_providers.<id>] tables, never scalars or [[array]] entries
- Validate each model_providers child is a table after external edits
- Let the library create provider entries instead of hand-writing them
- Diff config.toml against a known-good backup after manual restructuring
When it happens
Trigger: Any apply/write flow that ensures a provider exists when config.toml contains e.g. `model_providers.openai = "https://..."` or `[model_providers]` declared as an array of tables `[[model_providers]]` with that id.
Common situations: Users converting a provider into a bare base_url string to 'simplify' the config, following old docs that used array-of-tables syntax, or corrupted sections after manual merges.
Related errors
- 必须是 TOML table
- 必须是 TOML 表
- config.toml TOML 解析失败:
- config.toml TOML 解析失败:
- Dream Skin theme backup belongs to a different config.toml
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/95d5b034e60e32eb.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/relay_config.rs:3328
}
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 b1ed92e5e4)