{"record":{"id":"229340faf25b669a","repo":"Hmbown/CodeWhale","slug":"failed-to-read-mcp-config","errorCode":null,"errorMessage":"Failed to read MCP config {}: {}","messagePattern":"Failed to read MCP config (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/lib.rs","lineNumber":10123,"sourceCode":"            println!(\"  command: {exe_str}\");\n            println!(\n                \"  args:    {}serve --mcp\",\n                workspace.map_or(String::new(), |ws| format!(\"--workspace {ws} \"))\n            );\n            println!();\n            println!(\"Tip: Use `codewhale mcp validate` to test the connection.\");\n            println!(\"     Use `codewhale serve --http` for the HTTP/SSE runtime API instead.\");\n            Ok(())\n        }\n    }\n}\n\nfn load_mcp_config(path: &Path) -> Result<McpConfig> {\n    if !path.exists() {\n        return Ok(McpConfig::default());\n    }\n    let contents = std::fs::read_to_string(path)\n        .map_err(|e| anyhow::anyhow!(\"Failed to read MCP config {}: {}\", path.display(), e))?;\n    let cfg: McpConfig = serde_json::from_str(&contents).map_err(|_| {\n        anyhow::anyhow!(\n            \"Failed to parse MCP config {}; file contents were omitted\",\n            codewhale_config::quote_os_path(path)\n        )\n    })?;\n    Ok(cfg)\n}\n\n/// Diagnostic status for an MCP server entry.\n#[derive(Debug)]\nenum McpServerDoctorStatus {\n    Ok(String),\n    Warning(String),\n    Error(String),\n}\n\nimpl McpServerDoctorStatus {","sourceCodeStart":10105,"sourceCodeEnd":10141,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/lib.rs#L10105-L10141","documentation":"Thrown by `load_mcp_config` when the config file exists but cannot be read from disk (permissions, I/O error). The path and the underlying io::Error are included in the message. A non-existent path is not an error — it returns a default config.","triggerScenarios":"Any `codewhale mcp` subcommand that calls load_mcp_config when `std::fs::read_to_string(path)` fails: file exists but lacks read permission, path is a directory, or an OS-level I/O error occurs.","commonSituations":"Config file owned by another user or written by root; restrictive umask after running as a different user; the path points at a directory; disk/network filesystem errors; Windows file locking.","solutions":["Fix permissions on the config file (e.g. `chmod u+rw <path>` or chown to your user).","Confirm the path is a regular file, not a directory (`ls -l <path>`).","If it was created by root/sudo, chown it back to your user or recreate it as your user.","Check the wrapped OS error for the underlying cause (EACCES, EISDIR, EIO) and address it directly."],"exampleFix":"// before\nsudo codewhale mcp add myserver -- cmd   # creates root-owned config\n// after\ncodewhale mcp add myserver -- cmd\nsudo chown $USER ~/.config/codewhale/mcp.json  # repair if needed","handlingStrategy":"try-catch","validationCode":"let meta = std::fs::metadata(&path)?;\nif !meta.is_file() { eprintln!(\"config path is not a regular file: {}\", path.display()); }\nlet f = std::fs::OpenOptions::new().read(true).open(&path)?; // probe readability","typeGuard":null,"tryCatchPattern":"match std::fs::read_to_string(&path) {\n    Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => eprintln!(\"fix ownership/permissions on {}\", path.display()),\n    Err(e) => eprintln!(\"read failed: {e}\"),\n    Ok(_) => /* continue */,\n}","preventionTips":["Never run MCP CLI commands under sudo; repair ownership with chown instead.","Keep config files as regular files with user-owned permissions (0600).","Check `ls -l` on the config path when hitting read errors."],"tags":["filesystem","mcp","config","permissions"],"backgroundTag":"file-read-failed","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}