Hmbown/CodeWhale · error

MCP config path must be a regular file

Error message

MCP config path must be a regular file: {}

What it means

The MCP config loader validates that the given path is a plain regular file; symlinks and non-file entries (directories, sockets, fifos) are rejected. This prevents loading config through indirection that could bypass security or integrity expectations.

Solutions

  1. Replace the symlink with a real file (copy the target contents to the path)
  2. Point the MCP config path at a regular file instead of a directory or special file
  3. If a symlink is unavoidable, hard-link or copy the file rather than symlinking

Example fix

// before
ln -s ~/dotfiles/mcp.json ~/.codewhale/mcp.json
// after
cp ~/dotfiles/mcp.json ~/.codewhale/mcp.json
Defensive patterns

Strategy: validation

Validate before calling

let md = std::fs::symlink_metadata(path)?;
if md.file_type().is_symlink() || !md.is_file() {
    return Err(format!("{} must be a regular file", path.display()));
}

Type guard

fn is_regular_file(path: &Path) -> bool {
    std::fs::symlink_metadata(path)
        .map(|md| md.file_type().is_file()).unwrap_or(false)
}

Try / catch

match load_mcp_config(path) {
    Err(e) if e.to_string().contains("must be a regular file") => {
        eprintln!("resolve the symlink or point at a real file: {path:?}");
    }
    other => other?,
}

Prevention

When it happens

Trigger: Passing a path to the MCP config loader where metadata.file_type().is_symlink() is true or !is_file() — e.g. a symlinked ~/.codewhale/mcp.json, or pointing at a directory.

Common situations: Dotfile managers (stow, chezmoi, GNU stow) that symlink config files into the repo; accidentally passing a directory path; mounting the config as a socket/fifo.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/4a56b3369da46fd5. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp.rs:5347

        )
    })
}

/// Maximum bytes read from an MCP config file. Configs are kilobytes.
const MAX_MCP_CONFIG_BYTES: u64 = 1024 * 1024;

fn read_mcp_config_file(path: &Path) -> Result<Option<String>> {
    let metadata = match fs::symlink_metadata(path) {
        Ok(metadata) => metadata,
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
        Err(err) => {
            return Err(err)
                .with_context(|| format!("Failed to inspect MCP config {}", path.display()));
        }
    };
    let file_type = metadata.file_type();
    if file_type.is_symlink() || !file_type.is_file() {
        anyhow::bail!("MCP config path must be a regular file: {}", path.display());
    }

    let file = open_mcp_config_file(path)
        .with_context(|| format!("Failed to read MCP config {}", path.display()))?;
    let mut contents = String::new();
    file.take(MAX_MCP_CONFIG_BYTES + 1)
        .read_to_string(&mut contents)
        .with_context(|| format!("Failed to read MCP config {}", path.display()))?;
    if contents.len() as u64 > MAX_MCP_CONFIG_BYTES {
        anyhow::bail!("MCP config {} exceeds the 1 MiB limit", path.display());
    }
    Ok(Some(contents))
}

#[cfg(unix)]
fn open_mcp_config_file(path: &Path) -> std::io::Result<fs::File> {
    use std::os::unix::fs::OpenOptionsExt;

View on GitHub (pinned to 73e0f67d83)