{"record":{"id":"54f74b29e765854e","repo":"Hmbown/CodeWhale","slug":"automation-lock-must-not-be-a-reparse-point","errorCode":null,"errorMessage":"Automation lock must not be a reparse point","messagePattern":"Automation lock must not be a reparse point","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/automation_manager.rs","lineNumber":1036,"sourceCode":"        let file = options\n            .open(&path)\n            .with_context(|| format!(\"open {}\", path.display()))?;\n        let metadata = file.metadata()?;\n        if !metadata.is_file() {\n            bail!(\"Automation lock must be a regular file\");\n        }\n        #[cfg(unix)]\n        {\n            use std::os::unix::fs::MetadataExt as _;\n            if metadata.nlink() != 1 {\n                bail!(\"Automation lock must not have hard links\");\n            }\n        }\n        #[cfg(windows)]\n        {\n            use std::os::windows::fs::MetadataExt as _;\n            if metadata.file_attributes() & 0x400 != 0 {\n                bail!(\"Automation lock must not be a reparse point\");\n            }\n        }\n        Ok(fd_lock::RwLock::new(file))\n    }\n\n    fn with_transaction<T>(&self, operation: impl FnOnce() -> Result<T>) -> Result<T> {\n        let mut lock = self.open_lock(\"state.lock\")?;\n        let _guard = lock.write().context(\"lock automation state\")?;\n        operation()\n    }\n\n    /// Short read/modify/write transaction shared with scheduler admission.\n    /// Returning None leaves an absent record absent; it does not delete one.\n    pub(crate) fn edit_automation(\n        &self,\n        id: &str,\n        edit: impl FnOnce(Option<AutomationRecord>) -> Result<Option<AutomationRecord>>,\n    ) -> Result<Option<AutomationRecord>> {","sourceCodeStart":1018,"sourceCodeEnd":1054,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/automation_manager.rs#L1018-L1054","documentation":"On Windows, the manager checks the file attributes for FILE_ATTRIBUTE_REPARSE_POINT (0x400) and rejects the lock file if set. Reparse points (symlinks, junctions, mounts) could redirect the lock elsewhere or be raced, so they are not accepted as lock targets.","triggerScenarios":"Opening a lock path that is a Windows symlink, junction, OneDrive placeholder, DevDrive/mount reparse point, or any file with attribute 0x400 set.","commonSituations":"State directory moved onto a OneDrive-synced folder whose files are reparse points; junction-mounted state dirs; Dev Drive or WSL interop symlinks at the lock path.","solutions":["Replace the symlink/junction at the lock path with a real regular file (copy contents, delete the link)","Move the state directory out of OneDrive/placeholder-backed storage to a plain local path","Clear the reparse point attribute by recreating the file locally"],"exampleFix":"// PowerShell\n// before\nNew-Item -ItemType SymbolicLink -Path lock -Target real.lock  # reparse point, rejected\n// after\nCopy-Item real.lock lock -Force  # regular file","handlingStrategy":"validation","validationCode":"#[cfg(windows)]\n{\n    use std::os::windows::fs::MetadataExt;\n    let attrs = std::fs::metadata(&lock_path)?.file_attributes();\n    assert!(attrs & 0x400 == 0, \"lock file must not be a reparse point\");\n}","typeGuard":null,"tryCatchPattern":"if err.to_string().contains(\"reparse point\") {\n    std::fs::remove_file(&lock_path).ok(); // recreate as a real file\n}","preventionTips":["Keep app state out of OneDrive or other placeholder-backed folders","Do not place lock files behind symlinks or junctions on Windows","Check attributes with: attrib <path> /L"],"tags":["windows","filesystem","lock","security"],"backgroundTag":"unsupported-operation","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}