{"record":{"id":"4a56b3369da46fd5","repo":"Hmbown/CodeWhale","slug":"mcp-config-path-must-be-a-regular-file","errorCode":null,"errorMessage":"MCP config path must be a regular file: {}","messagePattern":"MCP config path must be a regular file: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/mcp.rs","lineNumber":5347,"sourceCode":"        )\n    })\n}\n\n/// Maximum bytes read from an MCP config file. Configs are kilobytes.\nconst MAX_MCP_CONFIG_BYTES: u64 = 1024 * 1024;\n\nfn read_mcp_config_file(path: &Path) -> Result<Option<String>> {\n    let metadata = match fs::symlink_metadata(path) {\n        Ok(metadata) => metadata,\n        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),\n        Err(err) => {\n            return Err(err)\n                .with_context(|| format!(\"Failed to inspect MCP config {}\", path.display()));\n        }\n    };\n    let file_type = metadata.file_type();\n    if file_type.is_symlink() || !file_type.is_file() {\n        anyhow::bail!(\"MCP config path must be a regular file: {}\", path.display());\n    }\n\n    let file = open_mcp_config_file(path)\n        .with_context(|| format!(\"Failed to read MCP config {}\", path.display()))?;\n    let mut contents = String::new();\n    file.take(MAX_MCP_CONFIG_BYTES + 1)\n        .read_to_string(&mut contents)\n        .with_context(|| format!(\"Failed to read MCP config {}\", path.display()))?;\n    if contents.len() as u64 > MAX_MCP_CONFIG_BYTES {\n        anyhow::bail!(\"MCP config {} exceeds the 1 MiB limit\", path.display());\n    }\n    Ok(Some(contents))\n}\n\n#[cfg(unix)]\nfn open_mcp_config_file(path: &Path) -> std::io::Result<fs::File> {\n    use std::os::unix::fs::OpenOptionsExt;\n","sourceCodeStart":5329,"sourceCodeEnd":5365,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/mcp.rs#L5329-L5365","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Replace the symlink with a real file (copy the target contents to the path)","Point the MCP config path at a regular file instead of a directory or special file","If a symlink is unavoidable, hard-link or copy the file rather than symlinking"],"exampleFix":"// before\nln -s ~/dotfiles/mcp.json ~/.codewhale/mcp.json\n// after\ncp ~/dotfiles/mcp.json ~/.codewhale/mcp.json","handlingStrategy":"validation","validationCode":"let md = std::fs::symlink_metadata(path)?;\nif md.file_type().is_symlink() || !md.is_file() {\n    return Err(format!(\"{} must be a regular file\", path.display()));\n}","typeGuard":"fn is_regular_file(path: &Path) -> bool {\n    std::fs::symlink_metadata(path)\n        .map(|md| md.file_type().is_file()).unwrap_or(false)\n}","tryCatchPattern":"match load_mcp_config(path) {\n    Err(e) if e.to_string().contains(\"must be a regular file\") => {\n        eprintln!(\"resolve the symlink or point at a real file: {path:?}\");\n    }\n    other => other?,\n}","preventionTips":["Don't symlink config files managed by dotfile tools; copy them instead","Use symlink_metadata (not metadata) in pre-checks so symlinks are detected","Validate the path is a file before handing it to the config loader"],"tags":["mcp","config","symlink","filesystem"],"backgroundTag":"invalid-argument-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}