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
- Fix the syntax error at the reported line/column of config.toml
- Pre-validate the file (taplo lint / python tomllib) before starting codex
- 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
- Use `codex mcp add` / `codex mcp remove` instead of hand-editing the mcp_servers section
- Parse-check config.toml with editor tooling (taplo, Even Better TOML) so syntax errors never reach runtime
- Remember the whole file must parse — an error outside mcp_servers still breaks MCP loading
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
- invalid requirement for MCP server `{server_name}` (set by {
- InvalidData
- failed to parse cloud config fragment {fragment}: {message}
- invalid cloud config fragment {fragment}: {message}
- invalid rules in requirements (set by {requirement_source}):
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/01222631fd0cefda.
Report an issue: GitHub.