Hmbown/CodeWhale · error

Failed to write MCP config {}: {}

Error message

Failed to write MCP config {}: {}

What it means

Raised by save_mcp_config when utils::write_atomic fails to persist the serialized MCP config. write_atomic creates a temp file next to the target, writes, fsyncs, and renames, so the error covers temp-file creation, write, fsync, or rename failures (the parent directory is created first, so directory creation rarely fails). The message includes the target path and the underlying io::Error. Note write_atomic applies Private (0600-style) permissions, which some filesystems reject.

Source

Thrown at crates/tui/src/lib.rs:8886

    }

    McpServerDoctorStatus::Ok(format!(
        "stdio server configured (command omitted; {} argument(s), {} environment binding(s))",
        server.args.len(),
        server.env.len()
    ))
}

fn save_mcp_config(path: &Path, cfg: &McpConfig) -> Result<()> {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent).with_context(|| {
            format!("Failed to create MCP config directory {}", parent.display())
        })?;
    }
    let rendered = serde_json::to_string_pretty(cfg)
        .map_err(|e| anyhow!("Failed to serialize MCP config: {e}"))?;
    crate::utils::write_atomic(path, rendered.as_bytes())
        .map_err(|e| anyhow!("Failed to write MCP config {}: {}", path.display(), e))?;
    Ok(())
}

fn run_sandbox_command(args: SandboxArgs) -> Result<()> {
    use crate::sandbox::{CommandSpec, SandboxManager};

    let SandboxCommand::Run {
        policy,
        network,
        writable_root,
        exclude_tmpdir,
        exclude_slash_tmp,
        cwd,
        timeout_ms,
        command,
    } = args.command;

    let policy = parse_sandbox_policy(

View on GitHub (pinned to 8880682c63)

Solutions

  1. Check ownership and permissions of the config directory named in the message (ls -ld) and chown/chmod it for the current user
  2. Verify the target path is not an existing directory and remove stale temp siblings left by failed writes
  3. Free disk space and retry the command that saves the config
  4. If the directory cannot be made writable, move the config path to a writable location (set XDG_CONFIG_HOME or the equivalent override) or rerun as the owning user

Example fix

# before: config dir owned by root, write fails
sudo codewhale mcp add my-server -- s npx -y server.js
# after: fix ownership, run as the user
sudo chown -R "$USER" ~/.config/codewhale
codewhale mcp add my-server -- npx -y server.js
Defensive patterns

Strategy: validation

Validate before calling

// Rust: verify the destination is writable before triggering the save
fn mcp_config_writable(path: &std::path::Path) -> bool {
    let parent = match path.parent() {
        Some(p) if !p.as_os_str().is_empty() => p.to_path_buf(),
        _ => std::path::PathBuf::from("."),
    };
    if std::fs::create_dir_all(&parent).is_err() || path.is_dir() {
        return false;
    }
    let probe = parent.join(format!(".mcp-write-probe-{}", std::process::id()));
    match std::fs::File::create(&probe) {
        Ok(_) => { let _ = std::fs::remove_file(&probe); true }
        Err(_) => false,
    }
}

Try / catch

// Branch on the underlying io error kind for recovery
if let Err(report) = save_mcp_config(&path, &cfg) {
    let io = report.chain().find_map(|c| c.downcast_ref::<std::io::Error>());
    match io.map(|e| e.kind()) {
        Some(std::io::ErrorKind::PermissionDenied) => { /* fix dir ownership, retry once */ }
        Some(std::io::ErrorKind::StorageFull) | Some(std::io::ErrorKind::WriteZero) => { /* free space, retry */ }
        _ => return Err(report),
    }
}

Prevention

When it happens

Trigger: Saving MCP config to a path whose parent exists but is not writable (EACCES), a path that names an existing directory, a full filesystem during the temp write/fsync, a read-only mount, or a filesystem that cannot apply private permission bits (FAT/exFAT, some network mounts).

Common situations: Config directory owned by root or another user, XDG config dir on a read-only mount, disk exhausted during `mcp add`, path colliding with an existing directory, macOS TCC or Windows controlled-folder-access blocking the config dir.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/b377662aacb12e99. Report an issue: GitHub.