xai-org/grok-build · error
config root is not a table
Error message
config root is not a table
What it means
save_mcp_disabled_tools reads the config TOML into a TomlValue and calls as_table_mut expecting the document root to be a table; if parsing produced a non-table value (scalar/array) this error fires. In practice a valid TOML document is always a table, so this mostly triggers when parse_existing fallback or file content is malformed in an unusual way, or defensively when the value type is wrong.
Source
Thrown at crates/codegen/xai-grok-shell/src/util/config/mcp.rs:644
pub const MANAGED_GATEWAY_DISABLED_CONNECTORS_KEY: &str = "__managed_gateway_connectors";
/// Persist `disabled_tools` for a server under `[disabled_mcp_tools]` in config.toml.
///
/// Uses a dedicated top-level section (not `[mcp_servers]`) to avoid creating
/// incomplete server entries that fail to deserialize for managed servers.
pub(crate) async fn save_mcp_disabled_tools(
server_name: &str,
disabled_tools: &[String],
) -> Result<()> {
let path = config_path();
let mut root: TomlValue = match tokio::fs::read_to_string(&path).await {
Ok(s) => toml::from_str(&s).unwrap_or(TomlValue::Table(TomlMap::new())),
Err(_) => TomlValue::Table(TomlMap::new()),
};
let table = root
.as_table_mut()
.ok_or_else(|| anyhow::anyhow!("config root is not a table"))?;
let section = table
.entry("disabled_mcp_tools")
.or_insert_with(|| TomlValue::Table(TomlMap::new()))
.as_table_mut()
.ok_or_else(|| anyhow::anyhow!("disabled_mcp_tools is not a table"))?;
if disabled_tools.is_empty() {
section.remove(server_name);
if section.is_empty() {
table.remove("disabled_mcp_tools");
}
} else {
let arr = disabled_tools
.iter()
.map(|s| TomlValue::String(s.clone()))
.collect();
section.insert(server_name.to_string(), TomlValue::Array(arr));View on GitHub (pinned to bc7f02eddd)
Solutions
- Open the config TOML and ensure the top level is a proper table (key = value sections), not a bare scalar or array.
- Delete or repair the malformed config file and let the tool recreate it.
- Validate the file with `toml` parsing (e.g. a quick script using the toml crate or a TOML linter) before running the save.
- If the loader can produce non-table roots, replace the error with a normalization step that coerces the root to an empty table.
Example fix
// before
let table = root.as_table_mut().ok_or_else(|| anyhow!("config root is not a table"))?;
// after — normalize instead of failing
if !root.is_table() {
root = TomlValue::Table(TomlMap::new());
}
let table = root.as_table_mut().unwrap(); Defensive patterns
Strategy: validation
Validate before calling
fn config_root_is_table(path: &Path) -> bool {
std::fs::read_to_string(path).ok()
.and_then(|s| s.parse::<toml::Value>().ok())
.map(|v| v.is_table())
.unwrap_or(false)
} Type guard
fn as_table(v: &toml::Value) -> Option<&toml::value::Table> { v.as_table() } Try / catch
match save_mcp_disabled_tools(&cfg, &server, &tools).await {
Err(e) if e.to_string().contains("config root is not a table") => {
backup_and_reset_config(&cfg_path)?;
save_mcp_disabled_tools(&cfg, &server, &tools).await
}
other => other,
} When it happens
Trigger: Calling save_mcp_disabled_tools when the loaded TOML root is not a TomlValue::Table — e.g. the file parses to a non-table value or a fallback path inserted a non-table root.
Common situations: Hand-edited config file whose top-level structure is invalid; a tool wrote a scalar/array TOML value; parser replaced a broken file with a non-table default.
Related errors
- Failed to load config: {e}
- disabled_mcp_tools is not a table
- refusing to overwrite unparseable {}: {}; fix the syntax bef
- Failed to create agent config: {e}
- Failed to create agent config: {e}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/90711161bbcf026b.
Report an issue: GitHub.