{"record":{"id":"7c101156425b8c79","repo":"Hmbown/CodeWhale","slug":"hardcoded-project-mcp-state-path-is-valid","errorCode":null,"errorMessage":"hardcoded project MCP state path is valid","messagePattern":"hardcoded project MCP state path is valid","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/spec.rs","lineNumber":725,"sourceCode":"}\n\nimpl std::ops::DerefMut for ToolContext {\n    fn deref_mut(&mut self) -> &mut Self::Target {\n        &mut self.execution\n    }\n}\n\nimpl ToolContext {\n    /// Create a new `ToolContext` with default settings.\n    #[must_use]\n    pub fn new(workspace: impl Into<PathBuf>) -> Self {\n        let workspace = workspace.into();\n        // Prefer .codewhale, fall back to .deepseek for project-local state\n        let notes_path = codewhale_config::resolve_project_state_dir(&workspace, \"notes.md\")\n            .expect(\"hardcoded project notes state path is valid\")\n            .1;\n        let mcp_config_path = codewhale_config::resolve_project_state_dir(&workspace, \"mcp.json\")\n            .expect(\"hardcoded project MCP state path is valid\")\n            .1;\n        Self::with_options(workspace, false, notes_path, mcp_config_path)\n    }\n\n    /// Create a `ToolContext` with all settings specified.\n    #[allow(dead_code)]\n    pub fn with_options(\n        workspace: impl Into<PathBuf>,\n        trust_mode: bool,\n        notes_path: impl Into<PathBuf>,\n        mcp_config_path: impl Into<PathBuf>,\n    ) -> Self {\n        let workspace = workspace.into();\n        let shell_manager = new_shared_shell_manager(workspace.clone());\n        let tool_authority = process_tool_authority();\n        let shell_policy = match tool_authority.as_deref() {\n            Some(cap) => cap.shell.shell_policy(),\n            None => ShellPolicy::Full,","sourceCodeStart":707,"sourceCodeEnd":743,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/spec.rs#L707-L743","documentation":"Panic constructing the default `ToolContext`, sibling of the notes-path expect two lines above: `resolve_project_state_dir(&workspace, \"mcp.json\")` fails for the same workspace-path reasons (empty path, `..` components, unresolvable cwd, non-NotFound canonicalize error). In practice the `notes.md` call at spec.rs:722 runs first, so hitting this exact line means the notes path resolved but the MCP path did not — only possible if intermediate filesystem state changed between the two calls (e.g. permissions altered or the workspace unmounted mid-construction).","triggerScenarios":"Same as the notes-path variant: `ToolContext::new(\"\")`, relative workspace with `..`, deleted cwd, permission failures — plus the narrow race where `.codewhale/`/`.deepseek/` state resolution succeeds for notes.md but the filesystem rejects the second lookup.","commonSituations":"Embedders/tests with degenerate workspace paths; filesystems being remounted or permission changes racing tool-context construction; NFS/FUSE mounts with intermittent EACCES.","solutions":["Pass a canonical absolute workspace path and construct the context once, early, before long-running turns.","Reject empty and `..`-containing workspace paths at your entry point.","Check permissions on every component of the workspace chain (`namei -l <workspace>` helps on Linux).","Use `ToolContext::with_options` with explicit, pre-validated paths when embedding."],"exampleFix":"// before\nlet mcp_config_path = codewhale_config::resolve_project_state_dir(&workspace, \"mcp.json\")\n    .expect(\"hardcoded project MCP state path is valid\").1;\n\n// after: resolve both state paths once, with a clear failure\nlet (notes_path, mcp_config_path) = (\n    codewhale_config::resolve_project_state_dir(&workspace, \"notes.md\")\n        .context(\"resolve project notes state path\")?.1,\n    codewhale_config::resolve_project_state_dir(&workspace, \"mcp.json\")\n        .context(\"resolve project MCP state path\")?.1,\n);","handlingStrategy":"validation","validationCode":"fn workspace_ok(ws: &std::path::Path) -> bool {\n    !ws.as_os_str().is_empty()\n        && !ws.components().any(|c| matches!(c, std::path::Component::ParentDir))\n        && ws.canonicalize().is_ok()\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Construct ToolContext once, early, from a canonicalized workspace.","Check permissions along the workspace chain (`namei -l <workspace>` on Linux) when construction fails.","Prefer ToolContext::with_options with explicit pre-validated paths when embedding."],"tags":["rust","filesystem","workspace-path","tool-context","mcp-config","panic","expect"],"backgroundTag":"invalid-workspace-path","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}