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

  1. Open the config TOML and ensure the top level is a proper table (key = value sections), not a bare scalar or array.
  2. Delete or repair the malformed config file and let the tool recreate it.
  3. Validate the file with `toml` parsing (e.g. a quick script using the toml crate or a TOML linter) before running the save.
  4. 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


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/90711161bbcf026b. Report an issue: GitHub.