{"record":{"id":"a59227d87bffc6e2","repo":"Hmbown/CodeWhale","slug":"session-goal-path-must-be-a-regular-file","errorCode":null,"errorMessage":"Session goal {path} must be a regular file","messagePattern":"Session goal (.+?) must be a regular file","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/session_manager.rs","lineNumber":1353,"sourceCode":"                io::ErrorKind::NotFound,\n                format!(\"Session goal store {} was not created\", dir.display()),\n            )\n        })\n    }\n\n    fn validated_session_goal_path(&self, session_id: &str) -> std::io::Result<PathBuf> {\n        let id = self.validated_session_id(session_id)?;\n        Ok(self.session_goals_dir().join(format!(\"{id}.json\")))\n    }\n\n    fn checked_existing_session_goal_file(path: &Path) -> std::io::Result<bool> {\n        let metadata = match fs::symlink_metadata(path) {\n            Ok(metadata) => metadata,\n            Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(false),\n            Err(error) => return Err(error),\n        };\n        if metadata.file_type().is_symlink() || !metadata.is_file() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\"Session goal {} must be a regular file\", path.display()),\n            ));\n        }\n        Ok(true)\n    }\n\n    fn validated_checkpoint_path(&self, session_id: &str) -> std::io::Result<PathBuf> {\n        let trimmed = self.validated_session_id(session_id)?;\n        // Reserved file names inside `checkpoints/` must never collide with a\n        // per-session checkpoint file.\n        if format!(\"{trimmed}.json\") == LEGACY_CHECKPOINT_FILE\n            || format!(\"{trimmed}.json\") == OFFLINE_QUEUE_FILE\n        {\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::InvalidInput,\n                format!(\"Session id '{trimmed}' collides with a reserved checkpoint file\"),\n            ));","sourceCodeStart":1335,"sourceCodeEnd":1371,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/session_manager.rs#L1335-L1371","documentation":"The goal-file reader validates each session goal file with symlink_metadata and requires a regular, non-symlink file; anything else is InvalidData 'Session goal ... must be a regular file'. NotFound maps to Ok(false) (no goal), but symlinks and directories at the goal path are refused. Like the directory check, this blocks reading goal contents through a linked path.","triggerScenarios":"Calling the goal existence/read check when the path holds a symlink (even to a valid file), a directory, or another non-regular file type (fifo, device).","commonSituations":"A user symlinks a goal file into a shared location; the goal path was replaced by a directory during a failed cleanup; sync tools substitute links for real files; tests creating directories at the goal path.","solutions":["Replace the symlink with a real file (copy the target's contents to the goal path)","Remove the directory or special file at that path and let the manager recreate it","Avoid syncing goal files via tools that create symlinks; copy instead"],"exampleFix":"// before\nln -s ~/shared/goal.json <data>/goals/<session>.json\n// after\ncp ~/shared/goal.json <data>/goals/<session>.json","handlingStrategy":"validation","validationCode":"let md = std::fs::symlink_metadata(&goal_path)?;\nif md.file_type().is_symlink() || !md.is_file() { return Err(anyhow!(\"goal must be a regular file\")); }","typeGuard":"fn is_regular_file(p: &std::path::Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| !m.file_type().is_symlink() && m.is_file()).unwrap_or(false)\n}","tryCatchPattern":"match manager.read_session_goal(id) {\n    Ok(Some(goal)) => use(goal),\n    Ok(None) => {}, // no goal set\n    Err(e) if e.to_string().contains(\"must be a regular file\") => eprintln!(\"Replace the symlink/dir at the goal path with a real file\"),\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Copy goal files rather than symlinking them into the data dir","Ensure cleanup routines delete files, not replace them with directories","Treat the data directory as app-owned: no manual link tricks"],"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"}