{"record":{"id":"088495a2dd687b8c","repo":"Hmbown/CodeWhale","slug":"session-goal-store-dir-must-be-a-real-directory","errorCode":null,"errorMessage":"Session goal store {dir} must be a real directory","messagePattern":"Session goal store (.+?) must be a real directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/session_manager.rs","lineNumber":1312,"sourceCode":"    }\n\n    fn checkpoints_dir(&self) -> PathBuf {\n        self.sessions_dir.join(\"checkpoints\")\n    }\n\n    fn session_goals_dir(&self) -> PathBuf {\n        self.sessions_dir.join(SESSION_GOALS_DIR)\n    }\n\n    fn checked_existing_session_goals_dir(&self) -> std::io::Result<Option<PathBuf>> {\n        let dir = self.session_goals_dir();\n        let metadata = match fs::symlink_metadata(&dir) {\n            Ok(metadata) => metadata,\n            Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None),\n            Err(error) => return Err(error),\n        };\n        if metadata.file_type().is_symlink() || !metadata.is_dir() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"Session goal store {} must be a real directory\",\n                    dir.display()\n                ),\n            ));\n        }\n        Ok(Some(dir))\n    }\n\n    fn ensure_session_goals_dir(&self) -> std::io::Result<PathBuf> {\n        if let Some(dir) = self.checked_existing_session_goals_dir()? {\n            return Ok(dir);\n        }\n        let dir = self.session_goals_dir();\n        match fs::create_dir(&dir) {\n            Ok(()) => {}\n            Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {}","sourceCodeStart":1294,"sourceCodeEnd":1330,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/session_manager.rs#L1294-L1330","documentation":"The session goal store loader requires the goals directory to be a real directory on disk: it uses symlink_metadata so symlinks are detected before resolution, and rejects both symlinks and non-directory entries with InvalidData. NotFound returns Ok(None) (no store), but a symlink pointing anywhere — even to a valid directory — is refused. This prevents goals being read from or written through a symlinked path.","triggerScenarios":"Calling the goal-store open/read path when the directory at `dir` exists but is a symlink, a regular file, or otherwise not a real directory per symlink_metadata.","commonSituations":"A user symlinks the goals directory into a synced/cloud folder for convenience; a previous run left a file where the directory belongs; container/volume mounts present the path as something other than a plain directory.","solutions":["Replace the symlink with a real directory (mkdir the target and move contents, then remove the link)","Remove any non-directory entry at that path and let the manager create the directory itself","Check what is mounted at that path (mounts, bind mounts, cloud-sync agents) and ensure it is a plain local directory"],"exampleFix":"// before\nln -s ~/Dropbox/codewhale-goals ~/.local/share/codewhale/goals\n// after\nmkdir -p ~/.local/share/codewhale/goals  # real directory, sync via tooling not symlink","handlingStrategy":"validation","validationCode":"let md = std::fs::symlink_metadata(&goals_dir)?;\nif md.file_type().is_symlink() || !md.is_dir() { return Err(anyhow!(\"goals dir must be a real directory\")); }","typeGuard":"fn is_real_dir(p: &std::path::Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| !m.file_type().is_symlink() && m.is_dir()).unwrap_or(false)\n}","tryCatchPattern":"match manager.open_goal_store() {\n    Ok(g) => use(g),\n    Err(e) if e.to_string().contains(\"must be a real directory\") => {\n        eprintln!(\"Remove the symlink at the goals path; the app does not follow symlinks\");\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Never symlink directories inside the app's data directory; use copies or configure the data root instead","Verify no sync/cloud tools replace data-dir entries with links","Check for leftover files at directory paths after failed installs"],"tags":["filesystem","symlink","validation","security"],"backgroundTag":"path-is-not-a-directory","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"}