Hmbown/CodeWhale · error · anyhow::Error
Runtime store file must not be a symlink: {}
Error message
Runtime store file must not be a symlink: {} What it means
reject_symlinked_store_file uses symlink_metadata and bails when a store file (event log, turn record, etc.) is a symlink (crates/tui/src/runtime_threads.rs:8916). Append-heavy write paths, rollbacks, and atomic renames assume a real regular file; symlinks can swap targets mid-run, defeating durability guarantees, so they are refused.
Source
Thrown at crates/tui/src/runtime_threads.rs:8916
Component::ParentDir => {
normalized.pop();
}
Component::Normal(part) => normalized.push(part),
}
}
if normalized.as_os_str().is_empty() {
PathBuf::from(".")
} else {
normalized
}
}
fn reject_symlinked_store_file(path: &Path) -> Result<()> {
let Ok(metadata) = fs::symlink_metadata(path) else {
return Ok(());
};
if metadata.file_type().is_symlink() {
bail!(
"Runtime store file must not be a symlink: {}",
path.display()
);
}
Ok(())
}
fn open_runtime_store_file(
path: &Path,
purpose: &str,
configure: impl FnOnce(&mut OpenOptions),
) -> Result<File> {
reject_symlinked_store_file(path)?;
let mut options = OpenOptions::new();
configure(&mut options);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt as _;View on GitHub (pinned to 0c42157ee5)
Solutions
- Delete the symlink and restore a real file at that path (copy the data back if needed)
- Relocate the entire store root via configuration instead of symlinking individual files
- If you need the data elsewhere, use an OS-level bind mount or move the whole directory
- Re-run the runtime after removing links; missing files are fine, symlinks are not
Example fix
# before $ ln -s /mnt/bigdisk/events.jsonl ~/.codewhale/runtime/events.jsonl # after # move the whole store instead of one file $ mv ~/.codewhale/runtime /mnt/bigdisk/runtime # then set the store root config to /mnt/bigdisk/runtime
Defensive patterns
Strategy: validation
Validate before calling
// Refuse to start when any store file is a symlink.
for file in store_dir.files() {
if let Ok(md) = std::fs::symlink_metadata(&file) {
if md.file_type().is_symlink() {
return Err(anyhow::anyhow!("symlinked store file: {}", file.display()));
}
}
} Type guard
fn is_regular_file(path: &Path) -> bool {
std::fs::symlink_metadata(path)
.map(|md| md.file_type().is_file())
.unwrap_or(false)
} Try / catch
match open_runtime_store(store_dir) {
Ok(store) => store,
Err(err) if err.to_string().contains("must not be a symlink") => {
// surface to the user: they must replace links with real files
}
Err(err) => return Err(err),
} Prevention
- Never symlink individual store files to other volumes or synced folders
- Relocate the whole store via the store-root setting instead
- Exclude store directories from dotfile/backup managers that recreate files as links
- After restoring backups, verify store files are regular files before starting
When it happens
Trigger: User symlinks an events file onto another disk, a RAM disk, or a synced folder; a dotfile/backup manager replaced store files with links; a restore script recreated files as symlinks
Common situations: Trying to move bulky event logs off a small disk; sharing one store between machines via symlinked files; migration tools that link instead of copy.
Related errors
- Runtime store directory 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/3b5d57628cfdc928.
Report an issue: GitHub.