openai/codex · error · std::io::Error

InvalidData

InvalidData

Error message

{}

What it means

load_global_mcp_servers reads $CODEX_HOME/config.toml directly (not through the layered config loader) and parses the whole file as a toml::Value. Any TOML syntax error anywhere — even outside [mcp_servers] — becomes io::Error(InvalidData) whose message is the raw toml parse error with line/column. A missing file returns an empty map, so this error always means 'the file exists but does not parse'.

Source

Thrown at codex-rs/config/src/mcp_edit.rs:20

use std::io::ErrorKind;
use std::path::Path;

use toml::Value as TomlValue;

use crate::CONFIG_TOML_FILE;
use crate::McpServerConfig;

pub async fn load_global_mcp_servers(
    codex_home: &Path,
) -> std::io::Result<BTreeMap<String, McpServerConfig>> {
    let config_path = codex_home.join(CONFIG_TOML_FILE);
    let raw = match tokio::fs::read_to_string(&config_path).await {
        Ok(raw) => raw,
        Err(err) if err.kind() == ErrorKind::NotFound => return Ok(BTreeMap::new()),
        Err(err) => return Err(err),
    };
    let parsed = toml::from_str::<TomlValue>(&raw)
        .map_err(|err| std::io::Error::new(ErrorKind::InvalidData, err))?;
    let Some(servers_value) = parsed.get("mcp_servers") else {
        return Ok(BTreeMap::new());
    };

    ensure_no_inline_bearer_tokens(servers_value)?;

    servers_value
        .clone()
        .try_into()
        .map_err(|err| std::io::Error::new(ErrorKind::InvalidData, err))
}

fn ensure_no_inline_bearer_tokens(value: &TomlValue) -> std::io::Result<()> {
    let Some(servers_table) = value.as_table() else {
        return Ok(());
    };

    for (server_name, server_value) in servers_table {

View on GitHub (pinned to 339751715c)

Solutions

  1. Fix the syntax error at the reported line/column of config.toml
  2. Pre-validate the file (taplo lint / python tomllib) before starting codex
  3. Prefer `codex mcp add` so the file is edited structurally, not by hand

Example fix

# before
[mcp_servers.search]
command = "npx
args = ["-y", "some-server"]

# after — close the quote
[mcp_servers.search]
command = "npx"
args = ["-y", "some-server"]
Defensive patterns

Strategy: validation

Validate before calling

use std::path::Path;

// Run before load_global_mcp_servers.
async fn config_toml_parses(codex_home: &Path) -> std::io::Result<()> {
    let raw = tokio::fs::read_to_string(codex_home.join("config.toml")).await?;
    toml::from_str::<toml::Value>(&raw)
        .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?;
    Ok(())
}

Try / catch

match load_global_mcp_servers(codex_home).await {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidData
        && !e.to_string().contains("mcp_servers.") => {
        // Whole-file TOML syntax error; the message carries line/column.
        // (InvalidData naming mcp_servers.<name> is a schema or token issue.)
    }
    Err(e) => return Err(e),
    Ok(servers) => { /* ... */ }
}

Prevention

When it happens

Trigger: Awaiting load_global_mcp_servers with any syntax error in config.toml: an unterminated string in an unrelated section, duplicate keys, or a malformed inline table under mcp_servers.

Common situations: Hand-editing MCP config and breaking another section of the same file; snippets pasted in YAML/JSON shape; another tool appending malformed fragments to config.toml.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/01222631fd0cefda. Report an issue: GitHub.