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

  1. Replace the symlink/junction with a real directory at the configured path
  2. Remove the non-directory entry so the manager can create the store itself
  3. 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

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


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)