Hmbown/CodeWhale · error · io::Error
late usage store must be a real directory
Error message
late usage store must be a real directory
What it means
The late usage store path checks its directory using metadata with a Windows reparse-point test and a Unix is_symlink test, and errors with InvalidData 'late usage store must be a real directory' if the path is linked or not a directory. NotFound is tolerated (the store is simply created later). As with the goal store, symlinked directories are refused outright.
Solutions
- Replace the symlink/junction with a real directory at the configured path
- Remove the non-directory entry so the manager can create the store itself
- Check Windows junction/reparse attributes if on Windows (fsutil / dir /AL) and rebuild the path normally
Example fix
// before mklink /D usage-store D:\other\usage // after mkdir usage-store (or robocopy the contents and delete the junction)
Defensive patterns
Strategy: validation
Validate before calling
let md = std::fs::metadata(&usage_dir)?;
let linked = md.file_type().is_symlink() || md.file_type() == std::fs::FileType::from(std::os::unix::fs::FileTypeExt::...) /* or on Windows check reparse */;
if linked || !md.is_dir() { return Err(anyhow!("usage store must be a real directory")); } Type guard
fn is_real_dir_unix(p: &std::path::Path) -> bool {
std::fs::symlink_metadata(p).map(|m| !m.file_type().is_symlink() && m.is_dir()).unwrap_or(false)
} Try / catch
match manager.open_late_usage_store() {
Ok(s) => use(s),
Err(e) if e.to_string().contains("must be a real directory") => eprintln!("The usage store path is a symlink/junction; recreate it as a real directory"),
Err(e) => return Err(e.into()),
} Prevention
- Do not redirect the usage store with symlinks or Windows junctions; move the configured root instead
- On Windows, check for reparse points with fsutil before relocating store directories
- Let the application create the store directory itself rather than pre-creating links
When it happens
Trigger: Opening/validating the late usage store when the path exists but is a symlink (including Windows junctions/reparse points) or a non-directory entry.
Common situations: Users redirecting the usage store via symlink to another volume; junction points on Windows setups; leftover files at the store path from a crashed setup.
Related errors
- CodewhalePet/1
- external credential path must name a non-reparse regular…
- Fleet file must be regular and not linked
- plugin file is a reparse point, hard link, or non-regular…
- reviewed plugin cwd is a reparse point or non-directory
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/2e2b3fd3d8a9cfc6.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/session_manager.rs:1412
&self.sessions_dir
}
fn late_usage_paths(&self, session_id: &str) -> io::Result<(PathBuf, PathBuf)> {
let session_id = self.validated_session_id(session_id)?;
let dir = self.sessions_dir.join(LATE_USAGE_DIR);
match fs::symlink_metadata(&dir) {
Ok(metadata) => {
#[cfg(windows)]
let linked = {
use std::os::windows::fs::MetadataExt as _;
metadata.file_attributes()
& windows_sys::Win32::Storage::FileSystem::FILE_ATTRIBUTE_REPARSE_POINT
!= 0
};
#[cfg(not(windows))]
let linked = metadata.file_type().is_symlink();
if linked || !metadata.is_dir() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"late usage store must be a real directory",
));
}
}
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
Err(error) => return Err(error),
}
Ok((
dir.join(format!("{session_id}.json")),
dir.join(format!("{session_id}.lock")),
))
}
/// Only mutations create accounting storage. Snapshot/list reads must work
/// for a healthy transcript even when no sidecar has ever been written.
fn ensure_late_usage_paths(&self, session_id: &str) -> io::Result<(PathBuf, PathBuf)> {
self.late_usage_paths(session_id)?;View on GitHub (pinned to 73e0f67d83)