{"record":{"id":"674942be9b79b3b8","repo":"Hmbown/CodeWhale","slug":"hardcoded-project-notes-state-path-is-valid","errorCode":null,"errorMessage":"hardcoded project notes state path is valid","messagePattern":"hardcoded project notes state path is valid","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/spec.rs","lineNumber":722,"sourceCode":"    fn deref(&self) -> &Self::Target {\n        &self.execution\n    }\n}\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();","sourceCodeStart":704,"sourceCodeEnd":740,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/spec.rs#L704-L740","documentation":"Panic constructing the default `ToolContext`: `resolve_project_state_dir(&workspace, \"notes.md\")` (config crate, lib.rs:5827) returns `Err` when the workspace path is unusable. Its `normalize_project_workspace` (lib.rs:6652) rejects empty paths and `..` components, fails when the current directory cannot be resolved (deleted/unreadable cwd), and fails on `canonicalize` errors other than NotFound (permissions along the path). The literal subdir is always safe, so the failure is entirely about the workspace argument.","triggerScenarios":"The engine or a tool constructs `ToolContext::new` with `PathBuf::new()`/`\"\"`; a workspace containing `..` components like `\"../repo\"`; a process whose cwd was deleted (`failed to resolve current directory for project workspace`); permission-denied components during canonicalization.","commonSituations":"Embedders and tests creating `ToolContext::new(Path::new(\"\"))`; running the TUI from a directory removed by another process; wrappers passing relative paths with `..`; permission-restricted mount points in the workspace chain.","solutions":["Pass a canonical absolute workspace path — run `std::fs::canonicalize` before constructing the context.","Reject empty and `..`-containing workspace paths at your entry point with a clear error.","If the cwd was deleted, restart from an existing directory.","Embedders can bypass resolution entirely with `ToolContext::with_options(workspace, ..., notes_path, mcp_config_path)` using explicit paths."],"exampleFix":"// before\nlet notes_path = codewhale_config::resolve_project_state_dir(&workspace, \"notes.md\")\n    .expect(\"hardcoded project notes state path is valid\").1;\n\n// after: validate/canonicalize the workspace before building the ToolContext\nlet workspace = std::fs::canonicalize(&workspace)\n    .context(\"workspace path must exist and be canonicalizable\")?;\nassert!(!workspace.components().any(|c| matches!(c, std::path::Component::ParentDir)));","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":["Pass canonical absolute workspace paths to ToolContext::new.","Strip `..` segments from workspace arguments at your entry point.","Do not run the engine from a deleted or unreadable cwd."],"tags":["rust","filesystem","workspace-path","tool-context","panic","expect"],"backgroundTag":"invalid-workspace-path","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}