Hmbown/CodeWhale · error · anyhow::Error

Automation lock must not be a reparse point

Error message

Automation lock must not be a reparse point

What it means

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.

Solutions

  1. Replace the symlink/junction at the lock path with a real regular file (copy contents, delete the link)
  2. Move the state directory out of OneDrive/placeholder-backed storage to a plain local path
  3. Clear the reparse point attribute by recreating the file locally

Example fix

// PowerShell
// before
New-Item -ItemType SymbolicLink -Path lock -Target real.lock  # reparse point, rejected
// after
Copy-Item real.lock lock -Force  # regular file
Defensive patterns

Strategy: validation

Validate before calling

#[cfg(windows)]
{
    use std::os::windows::fs::MetadataExt;
    let attrs = std::fs::metadata(&lock_path)?.file_attributes();
    assert!(attrs & 0x400 == 0, "lock file must not be a reparse point");
}

Try / catch

if err.to_string().contains("reparse point") {
    std::fs::remove_file(&lock_path).ok(); // recreate as a real file
}

Prevention

When it happens

Trigger: Opening a lock path that is a Windows symlink, junction, OneDrive placeholder, DevDrive/mount reparse point, or any file with attribute 0x400 set.

Common situations: 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.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/54f74b29e765854e. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/automation_manager.rs:1036

        let file = options
            .open(&path)
            .with_context(|| format!("open {}", path.display()))?;
        let metadata = file.metadata()?;
        if !metadata.is_file() {
            bail!("Automation lock must be a regular file");
        }
        #[cfg(unix)]
        {
            use std::os::unix::fs::MetadataExt as _;
            if metadata.nlink() != 1 {
                bail!("Automation lock must not have hard links");
            }
        }
        #[cfg(windows)]
        {
            use std::os::windows::fs::MetadataExt as _;
            if metadata.file_attributes() & 0x400 != 0 {
                bail!("Automation lock must not be a reparse point");
            }
        }
        Ok(fd_lock::RwLock::new(file))
    }

    fn with_transaction<T>(&self, operation: impl FnOnce() -> Result<T>) -> Result<T> {
        let mut lock = self.open_lock("state.lock")?;
        let _guard = lock.write().context("lock automation state")?;
        operation()
    }

    /// Short read/modify/write transaction shared with scheduler admission.
    /// Returning None leaves an absent record absent; it does not delete one.
    pub(crate) fn edit_automation(
        &self,
        id: &str,
        edit: impl FnOnce(Option<AutomationRecord>) -> Result<Option<AutomationRecord>>,
    ) -> Result<Option<AutomationRecord>> {

View on GitHub (pinned to 73e0f67d83)