Hmbown/CodeWhale · error
MCP config path must be a regular file: {}
Error message
MCP config path must be a regular file: {} What it means
read_mcp_config_file stats the config path with symlink_metadata and rejects anything that is not a plain regular file: symlinks (even to regular files), directories, FIFOs, and device nodes all fail. This blocks symlink-swap attacks against the file that holds server commands, env secrets, and reviewed-plugin settings.
Source
Thrown at crates/tui/src/mcp.rs:3571
anyhow::anyhow!(
"Failed to parse MCP config {}; file contents were omitted",
codewhale_config::quote_os_path(path)
)
})
}
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 mut file = open_mcp_config_file(path)
.with_context(|| format!("Failed to read MCP config {}", path.display()))?;
let mut contents = String::new();
file.read_to_string(&mut contents)
.with_context(|| format!("Failed to read MCP config {}", 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;
fs::OpenOptions::new()
.read(true)
.custom_flags(libc::O_NOFOLLOW)
.open(path)View on GitHub (pinned to 8880682c63)
Solutions
- Replace the symlink with the real file (copy the target into place) or use a hardlink/bind-mount instead
- If a dotfiles manager owns the file, switch it to copy/template mode rather than symlink
- Inspect with ls -l and remove any non-regular file occupying the path
Example fix
# before ~/.codewhale/mcp.json -> /home/me/dotfiles/mcp.json (symlink) # after mv ~/.codewhale/mcp.json ~/.codewhale/mcp.json.bak cp /home/me/dotfiles/mcp.json ~/.codewhale/mcp.json
Defensive patterns
Strategy: validation
Validate before calling
let meta = std::fs::symlink_metadata(&mcp_json_path)?;
let ft = meta.file_type();
if ft.is_symlink() || !ft.is_file() {
anyhow::bail!("MCP config path must be a real file, not a symlink or special file");
}
codewhale_tui::mcp::load_config(&mcp_json_path)?; Type guard
fn is_regular_config_file(path: &std::path::Path) -> bool {
std::fs::symlink_metadata(path)
.map(|m| !m.file_type().is_symlink() && m.file_type().is_file())
.unwrap_or(false)
} Try / catch
match load_config(&path) {
Err(e) if e.to_string().contains("must be a regular file") => {
// resolve the symlink target and copy it into place, then retry
}
other => other,
} Prevention
- Configure dotfiles managers to copy/template mcp.json instead of symlinking it
- Never point the config path at shared/ephemeral files (FIFOs, sockets, directories)
- Check the path with ls -l during environment setup scripts
When it happens
Trigger: Pointing the MCP config path at a symlink (e.g. ~/.codewhale/mcp.json linking into a dotfiles repo); the path being a directory or a named pipe.
Common situations: Dotfiles managers that symlink configs into place; provisioning scripts that accidentally create the path as a directory; container setups that expose the config through a symlink.
Related errors
- Failed to write MCP config {}: {}
- MCP config path cannot contain '..' components
- CODEWHALE_SMOKE_ASSETS_DIR is not a directory: ${releaseAsse
- invalid JSON payload at key {MCP_SERVER_DEFINITIONS_KEY}; co
- invalid MCP server definition list in key {MCP_SERVER_DEFINI
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/4a56b3369da46fd5.
Report an issue: GitHub.