xai-org/grok-build · error
failed to read {}: {e}
Error message
failed to read {}: {e} What it means
write_toml_table_if_changed reads the existing TOML file before rewriting it. NotFound is tolerated (empty for user configs, no-op otherwise), but any other read error is wrapped in this message. It is an I/O-level failure (permissions, path issues, is-a-directory), not a parse failure.
Source
Thrown at crates/codegen/xai-grok-shell/src/util/config/mcp.rs:786
/// files (no wipe-to-empty), unique tmp + mode preserve via
/// [`super::persist::atomic_write_string`], and the user-config write lock.
async fn write_toml_table_if_changed(
path: &std::path::Path,
f: impl FnOnce(&mut TomlMap<String, TomlValue>),
) -> Result<bool> {
let is_user = path == config_path().as_path();
let _guard = if is_user {
Some(super::persist::lock_config_writes().await)
} else {
None
};
let original = match tokio::fs::read_to_string(path).await {
Ok(s) => s,
Err(e) if e.kind() == std::io::ErrorKind::NotFound && is_user => String::new(),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(false),
Err(e) => {
return Err(anyhow::anyhow!("failed to read {}: {e}", path.display()));
}
};
let mut root = match super::persist::parse_existing_config_toml(&original) {
Ok(v) => v,
Err(parse_err) => {
return Err(anyhow::anyhow!(
"refusing to overwrite unparseable {}: {}; fix the syntax before retrying",
path.display(),
parse_err
));
}
};
let before = toml::to_string_pretty(&root)?;
let table = root
.as_table_mut()
.ok_or_else(|| anyhow::anyhow!("config root is not a table"))?;
f(table);
let toml_str = toml::to_string_pretty(&root)?;View on GitHub (pinned to bc7f02eddd)
Solutions
- Check the chained io error ({e}) and fix the OS-level cause (chmod/chown the file, free the mount).
- Ensure the config path is a regular file, not a directory; remove/replace it if so.
- Run the process as a user with read access to the config file.
- If the file is unreadable and disposable, move it aside (backup) and let the tool write a fresh one.
Example fix
// before
Err(e) => { return Err(anyhow!("failed to read {}: {e}", path.display())); }
// after — add context for the operator
Err(e) => {
return Err(e).with_context(|| format!(
"failed to read {}: check file permissions and that it is a regular file",
path.display()
));
} Defensive patterns
Strategy: validation
Validate before calling
fn config_readable_file(path: &Path) -> Result<(), String> {
let md = std::fs::metadata(path).map_err(|e| e.to_string())?;
if !md.is_file() { return Err(format!("{} is not a regular file", path.display())); }
std::fs::File::open(path).map(|_| ()).map_err(|e| e.to_string())
}
// invoke before save_mcp_server_enabled_in / save_user_mcp_server_enabled Try / catch
match save_user_mcp_server_enabled(name, enabled).await {
Err(e) if e.to_string().starts_with("failed to read ") => {
log::error!("config unreadable: {e}; check permissions/path is a file");
fix_permissions_or_relocate(&cfg_path)?;
save_user_mcp_server_enabled(name, enabled).await
}
other => other,
} Prevention
- Ensure the config path is always a regular file, never a directory
- Run the tool as a user with read/write access to the config
- Avoid read-only or network mounts for config directories
- Check the chained errno (EACCES, EISDIR) to pick the right fix
When it happens
Trigger: Calling save_mcp_server_enabled_in / save_user_mcp_server_enabled when reading the config path fails with a non-NotFound I/O error — e.g. Permission denied, the path is a directory, or a device error.
Common situations: Config file owned by another user / read-only mount; a directory was accidentally created at the config path; network home directories temporarily unavailable.
Related errors
- failed to write {}: {e}
- failed to open {}: {e}
- failed to replace {}: {e}
- {}
- journal is not a regular file: {}
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/d914e6e020e962d1.
Report an issue: GitHub.