Hmbown/CodeWhale · error · anyhow::Error
Runtime store directory must not be a symlink: {}
Error message
Runtime store directory must not be a symlink: {} What it means
reject_symlinked_store_dir (called by ensure_runtime_store_dir after create_dir_all) bails when the store directory itself is a symlink (crates/tui/src/runtime_threads.rs:9032). Directory-level symlinks can be retargeted between the existence check and writes, breaking the guarantee that events and turns land in one canonical, append-only location.
Source
Thrown at crates/tui/src/runtime_threads.rs:9032
std::thread::sleep(EVENT_TRANSACTION_LOCK_POLL.min(timeout - elapsed));
Ok(())
}
fn rollback_failed_event_append_handle(rollback_file: &File, original_len: u64) -> Result<()> {
rollback_file
.set_len(original_len)
.context("Failed to roll back Runtime event")?;
rollback_file
.sync_all()
.context("Failed to sync Runtime event rollback")
}
fn reject_symlinked_store_dir(path: &Path) -> Result<()> {
let Ok(metadata) = fs::symlink_metadata(path) else {
return Ok(());
};
if metadata.file_type().is_symlink() {
bail!(
"Runtime store directory must not be a symlink: {}",
path.display()
);
}
if !metadata.is_dir() {
bail!("Runtime store path must be a directory: {}", path.display());
}
Ok(())
}
fn ensure_runtime_store_dir(path: &Path) -> Result<()> {
fs::create_dir_all(path).with_context(|| format!("Failed to create {}", path.display()))?;
reject_symlinked_store_dir(path)
}
fn read_complete_event(
reader: &mut impl BufRead,
path: &Path,View on GitHub (pinned to 0c42157ee5)
Solutions
- Replace the symlink with a real directory and move the actual data there
- Configure the store root to point directly at the real target directory
- Use a bind mount (Linux) or the platform's supported volume mechanism instead of a symlink
- Remove the link, let create_dir_all make a real directory, then migrate contents
Example fix
# before $ rm -rf ~/.codewhale/runtime && ln -s /data/codewhale ~/.codewhale/runtime # after $ rm ~/.codewhale/runtime $ mkdir -p ~/.codewhale/runtime $ cp -a /data/codewhale/. ~/.codewhale/runtime/ # or: point the store-root setting at /data/codewhale directly
Defensive patterns
Strategy: validation
Validate before calling
// Validate the store directory shape before runtime start.
let md = std::fs::symlink_metadata(&store_dir)
.context("store dir metadata")?;
if md.file_type().is_symlink() {
return Err(anyhow::anyhow!("store dir is a symlink"));
}
if !md.is_dir() {
return Err(anyhow::anyhow!("store path is not a directory"));
} Type guard
fn is_real_dir(path: &Path) -> bool {
std::fs::symlink_metadata(path)
.map(|md| md.is_dir() && !md.file_type().is_symlink())
.unwrap_or(false)
} Try / catch
match RuntimeStore::open(store_dir) {
Ok(store) => store,
Err(err) if err.to_string().contains("must not be a symlink") => {
// Replace the link with a real dir, then retry once.
replace_symlink_with_dir(&store_dir)?;
RuntimeStore::open(store_dir)?
}
Err(err) => return Err(err),
} Prevention
- Move state with the store-root config or OS bind mounts, never directory symlinks
- Audit container/Nix setups that link state directories into volumes
- Add a startup doctor check that flags symlinked state directories
- Document the supported relocation mechanism for users wanting data on another disk
When it happens
Trigger: User symlinks ~/.codewhale/runtime (or equivalent) to another volume, a synced folder, or a tmpfs; dotfile managers linking state directories; container images linking the store to a volume path
Common situations: Moving state to a bigger disk via symlink; Docker setups linking store dirs; Nix/home-manager configs that manage state dirs as symlinks.
Related errors
- Runtime store file must not be a symlink: {}
- Runtime store root cannot be empty
- Runtime store root cannot contain '..' components
- Runtime store path must be a directory: {}
- config path must not be a symlink: {}
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/b075f3a749f64545.
Report an issue: GitHub.