BigPizzaV3/CodexPlusPlus · error
must be a TOML table
Error message
{key} must be a TOML table What it means
table_mut_or_insert ensures a top-level key in a config.toml DocumentMut is a table, replacing non-conflicting values with a new table, and returns a mutable reference. If the key exists as an array or other non-table item that cannot be coerced (as_table_mut fails after the insert attempt), this error is thrown rather than overwriting user data blindly.
Solutions
- Locate the key in config.toml and convert it to a proper table: change `key = value` to `[key]` with sub-keys.
- Rename the conflicting key if it was intentional user data, then re-run the merge.
- Back up config.toml before merges so a type conflict can be recovered.
- Add a pre-merge validation pass that rejects configs with non-table values for managed keys, with a clear message.
Example fix
// before: type conflict plugins = true // after: table form expected by merge [plugins] enabled = true
Defensive patterns
Strategy: validation
Validate before calling
if let Some(v) = doc.get(key) {
if v.as_table().is_none() {
eprintln!("config key '{key}' must be a [table], found: {v}");
}
} Type guard
fn is_table(doc: &toml_edit::DocumentMut, key: &str) -> bool {
doc.get(key).map_or(true, |item| item.as_table().is_some())
} Try / catch
match merge_marketplace_configs_and_plugins_into_text(&config) {
Ok(merged) => {},
Err(e) if e.to_string().ends_with("must be a TOML table") => {
eprintln!("{e}; fix the offending key in config.toml to use [section] syntax");
}
Err(e) => return Err(e),
} Prevention
- Declare managed keys as [section] tables, never as scalar or array values
- Back up config.toml before running merges
- Lint config.toml after manual edits to catch type conflicts early
When it happens
Trigger: merge_marketplace_configs_and_plugins_into_text calls table_mut_or_insert for a key (e.g. a marketplace or plugin section name) that already exists in config.toml as a non-table TOML value (string, integer, array of tables handled oddly, etc.).
Common situations: User declared something like plugins = "enabled" instead of [plugins]; a list value conflicts with the expected table; an upstream config writer emitted arrays where tables were expected.
Related errors
- config.toml TOML 解析失败:
- config.toml TOML 解析失败:
- Dream Skin theme backup belongs to a different config.toml
- 上下文 id 不能为空
- 必须是 TOML table
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/58915ccf8846e846.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/plugin_marketplace.rs:859
if contents.trim().is_empty() {
Ok(DocumentMut::new())
} else {
contents
.parse::<DocumentMut>()
.map_err(|error| anyhow::anyhow!("config.toml TOML parse failed: {error}"))
}
}
fn table_mut_or_insert<'a>(doc: &'a mut DocumentMut, key: &str) -> anyhow::Result<&'a mut Table> {
if !doc.as_table().contains_key(key) {
doc[key] = toml_edit::table();
}
if doc.get(key).and_then(Item::as_table).is_none() {
doc[key] = toml_edit::table();
}
doc.get_mut(key)
.and_then(Item::as_table_mut)
.ok_or_else(|| anyhow::anyhow!("{key} must be a TOML table"))
}
fn ensure_trailing_newline(mut contents: String) -> String {
if !contents.ends_with('\n') {
contents.push('\n');
}
contents
}
#[cfg(test)]
mod tests {
use super::*;
fn expected_marketplace_path(path: &Path) -> String {
if cfg!(windows) {
format!(r"\\?\{}", path.display())
} else {
path.to_string_lossy().into_owned()View on GitHub (pinned to b1ed92e5e4)