Hmbown/CodeWhale · error
Session goal must be a regular file
Error message
Session goal {path} must be a regular file What it means
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.
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
Example fix
// before ln -s ~/shared/goal.json <data>/goals/<session>.json // after cp ~/shared/goal.json <data>/goals/<session>.json
Defensive patterns
Strategy: validation
Validate before calling
let md = std::fs::symlink_metadata(&goal_path)?;
if md.file_type().is_symlink() || !md.is_file() { return Err(anyhow!("goal must be a regular file")); } Type guard
fn is_regular_file(p: &std::path::Path) -> bool {
std::fs::symlink_metadata(p).map(|m| !m.file_type().is_symlink() && m.is_file()).unwrap_or(false)
} Try / catch
match manager.read_session_goal(id) {
Ok(Some(goal)) => use(goal),
Ok(None) => {}, // no goal set
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"),
Err(e) => return Err(e.into()),
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- CodewhalePet/1
- Session goal store must be a real directory
- built-in plugin path may not be a symbolic link or reparse…
- could not securely open
- external credential path must name a non-reparse regular…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/a59227d87bffc6e2.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/session_manager.rs:1353
io::ErrorKind::NotFound,
format!("Session goal store {} was not created", dir.display()),
)
})
}
fn validated_session_goal_path(&self, session_id: &str) -> std::io::Result<PathBuf> {
let id = self.validated_session_id(session_id)?;
Ok(self.session_goals_dir().join(format!("{id}.json")))
}
fn checked_existing_session_goal_file(path: &Path) -> std::io::Result<bool> {
let metadata = match fs::symlink_metadata(path) {
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(false),
Err(error) => return Err(error),
};
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("Session goal {} must be a regular file", path.display()),
));
}
Ok(true)
}
fn validated_checkpoint_path(&self, session_id: &str) -> std::io::Result<PathBuf> {
let trimmed = self.validated_session_id(session_id)?;
// Reserved file names inside `checkpoints/` must never collide with a
// per-session checkpoint file.
if format!("{trimmed}.json") == LEGACY_CHECKPOINT_FILE
|| format!("{trimmed}.json") == OFFLINE_QUEUE_FILE
{
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
format!("Session id '{trimmed}' collides with a reserved checkpoint file"),
));View on GitHub (pinned to 73e0f67d83)