{"record":{"id":"b075f3a749f64545","repo":"Hmbown/CodeWhale","slug":"runtime-store-directory-must-not-be-a-symlink","errorCode":null,"errorMessage":"Runtime store directory must not be a symlink: {}","messagePattern":"Runtime store directory must not be a symlink: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":9032,"sourceCode":"    std::thread::sleep(EVENT_TRANSACTION_LOCK_POLL.min(timeout - elapsed));\n    Ok(())\n}\n\nfn rollback_failed_event_append_handle(rollback_file: &File, original_len: u64) -> Result<()> {\n    rollback_file\n        .set_len(original_len)\n        .context(\"Failed to roll back Runtime event\")?;\n    rollback_file\n        .sync_all()\n        .context(\"Failed to sync Runtime event rollback\")\n}\n\nfn reject_symlinked_store_dir(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 directory must not be a symlink: {}\",\n            path.display()\n        );\n    }\n    if !metadata.is_dir() {\n        bail!(\"Runtime store path must be a directory: {}\", path.display());\n    }\n    Ok(())\n}\n\nfn ensure_runtime_store_dir(path: &Path) -> Result<()> {\n    fs::create_dir_all(path).with_context(|| format!(\"Failed to create {}\", path.display()))?;\n    reject_symlinked_store_dir(path)\n}\n\nfn read_complete_event(\n    reader: &mut impl BufRead,\n    path: &Path,","sourceCodeStart":9014,"sourceCodeEnd":9050,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/runtime_threads.rs#L9014-L9050","documentation":"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.","triggerScenarios":"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","commonSituations":"Moving state to a bigger disk via symlink; Docker setups linking store dirs; Nix/home-manager configs that manage state dirs as symlinks.","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"],"exampleFix":"# before\n$ rm -rf ~/.codewhale/runtime && ln -s /data/codewhale ~/.codewhale/runtime\n\n# after\n$ rm ~/.codewhale/runtime\n$ mkdir -p ~/.codewhale/runtime\n$ cp -a /data/codewhale/. ~/.codewhale/runtime/\n# or: point the store-root setting at /data/codewhale directly","handlingStrategy":"validation","validationCode":"// Validate the store directory shape before runtime start.\nlet md = std::fs::symlink_metadata(&store_dir)\n    .context(\"store dir metadata\")?;\nif md.file_type().is_symlink() {\n    return Err(anyhow::anyhow!(\"store dir is a symlink\"));\n}\nif !md.is_dir() {\n    return Err(anyhow::anyhow!(\"store path is not a directory\"));\n}","typeGuard":"fn is_real_dir(path: &Path) -> bool {\n    std::fs::symlink_metadata(path)\n        .map(|md| md.is_dir() && !md.file_type().is_symlink())\n        .unwrap_or(false)\n}","tryCatchPattern":"match RuntimeStore::open(store_dir) {\n    Ok(store) => store,\n    Err(err) if err.to_string().contains(\"must not be a symlink\") => {\n        // Replace the link with a real dir, then retry once.\n        replace_symlink_with_dir(&store_dir)?;\n        RuntimeStore::open(store_dir)?\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["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"],"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"}