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

  1. Check the chained io error ({e}) and fix the OS-level cause (chmod/chown the file, free the mount).
  2. Ensure the config path is a regular file, not a directory; remove/replace it if so.
  3. Run the process as a user with read access to the config file.
  4. 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

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


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