{"record":{"id":"3b5d57628cfdc928","repo":"Hmbown/CodeWhale","slug":"runtime-store-file-must-not-be-a-symlink","errorCode":null,"errorMessage":"Runtime store file must not be a symlink: {}","messagePattern":"Runtime store file must not be a symlink: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":8916,"sourceCode":"            Component::ParentDir => {\n                normalized.pop();\n            }\n            Component::Normal(part) => normalized.push(part),\n        }\n    }\n    if normalized.as_os_str().is_empty() {\n        PathBuf::from(\".\")\n    } else {\n        normalized\n    }\n}\n\nfn reject_symlinked_store_file(path: &Path) -> Result<()> {\n    let Ok(metadata) = fs::symlink_metadata(path) else {\n        return Ok(());\n    };\n    if metadata.file_type().is_symlink() {\n        bail!(\n            \"Runtime store file must not be a symlink: {}\",\n            path.display()\n        );\n    }\n    Ok(())\n}\n\nfn open_runtime_store_file(\n    path: &Path,\n    purpose: &str,\n    configure: impl FnOnce(&mut OpenOptions),\n) -> Result<File> {\n    reject_symlinked_store_file(path)?;\n    let mut options = OpenOptions::new();\n    configure(&mut options);\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::OpenOptionsExt as _;","sourceCodeStart":8898,"sourceCodeEnd":8934,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/runtime_threads.rs#L8898-L8934","documentation":"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.","triggerScenarios":"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","commonSituations":"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.","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"],"exampleFix":"# before\n$ ln -s /mnt/bigdisk/events.jsonl ~/.codewhale/runtime/events.jsonl\n\n# after\n# move the whole store instead of one file\n$ mv ~/.codewhale/runtime /mnt/bigdisk/runtime\n# then set the store root config to /mnt/bigdisk/runtime","handlingStrategy":"validation","validationCode":"// Refuse to start when any store file is a symlink.\nfor file in store_dir.files() {\n    if let Ok(md) = std::fs::symlink_metadata(&file) {\n        if md.file_type().is_symlink() {\n            return Err(anyhow::anyhow!(\"symlinked store file: {}\", file.display()));\n        }\n    }\n}","typeGuard":"fn is_regular_file(path: &Path) -> bool {\n    std::fs::symlink_metadata(path)\n        .map(|md| md.file_type().is_file())\n        .unwrap_or(false)\n}","tryCatchPattern":"match open_runtime_store(store_dir) {\n    Ok(store) => store,\n    Err(err) if err.to_string().contains(\"must not be a symlink\") => {\n        // surface to the user: they must replace links with real files\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["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"],"tags":["rust","filesystem","symlink","store","configuration"],"backgroundTag":"symlink-rejected-storage","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}