astrid-runtime/astrid · error

workspace selection changed after validation

Error message

workspace selection changed after validation

What it means

WorkspaceSelection::verify re-resolves the stored project_root and compares the freshly computed project_root and state_dir against the values captured at selection time; any difference raises InvalidData 'workspace selection changed after validation'. It is a TOCTOU guard: if the workspace root was moved, replaced, re-mounted, or its state path re-pointed after the original validation, subsequent operations are refused rather than acting on an untrusted tree. verify is invoked by resolve_descendant, ensure_state_dir, and ensure_directory before touching the filesystem.

Source

Thrown at crates/astrid-core/src/workspace_security.rs:233

            }
        }
        Ok(self.state_dir.join(relative))
    }

    /// Re-check that the selected state path has not been redirected.
    ///
    /// A missing state directory remains valid. This permits a checked
    /// selection to be created before initialization while still rejecting a
    /// later symlink or non-directory replacement.
    ///
    /// # Errors
    ///
    /// Returns an error if the project root or state path no longer satisfies
    /// the original selection.
    pub fn verify(&self) -> io::Result<()> {
        let current = Self::resolve(&self.project_root, self.layout.clone())?;
        if current.project_root != self.project_root || current.state_dir != self.state_dir {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "workspace selection changed after validation",
            ));
        }
        Ok(())
    }

    /// Create the selected state directory and verify it again afterwards.
    ///
    /// # Errors
    ///
    /// Returns an error if creation fails or the path is redirected before or
    /// after creation.
    pub fn ensure_state_dir(&self) -> io::Result<()> {
        self.verify()?;
        #[cfg(windows)]
        {
            crate::platform_fs::ensure_private_directory(&self.state_dir)?;

View on GitHub (pinned to affd8760f4)

Solutions

  1. Re-create the WorkspaceSelection (call WorkspaceSelection::resolve again) and retry the operation with the fresh selection.
  2. Do not cache a selection across workspace moves; re-resolve whenever the project root may have changed.
  3. Check for concurrent processes moving or re-mounting the workspace and serialize such maintenance with library usage.
  4. If the root is deliberately remapped, update all held selections to the new canonical root instead of reusing stale ones.

Example fix

// before: reuse stale selection after moving the project
std::fs::rename("/srv/app", "/srv/app-v2");
ws.ensure_directory(Path::new("out"))?; // fails: selection changed
// after
let ws = WorkspaceSelection::resolve(Path::new("/srv/app-v2"), layout)?;
ws.ensure_directory(Path::new("out"))?;
Defensive patterns

Strategy: try-catch

Validate before calling

let fresh = WorkspaceSelection::resolve(&sel.project_root, sel.layout.clone())?;
let still_valid = fresh.project_root == sel.project_root && fresh.state_dir == sel.state_dir;

Try / catch

match op(&ws) {
    Err(e) if e.to_string().contains("workspace selection changed after validation") => {
        let ws = WorkspaceSelection::resolve(&root, layout.clone())?;
        op(&ws)?
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling resolve_directory/resolve_file/ensure_state_dir/ensure_directory after the workspace root was renamed, deleted-and-recreated at a different canonical path, re-mounted, or when a symlink/hash preimage changed what resolve() computes for state_dir (layout-dependent state paths shifting because the canonical root changed).

Common situations: Long-lived daemon holding a WorkspaceSelection while the project is moved or checked out afresh (new inode, canonical path changes via symlink retarget); containers restarting with volumes re-mounted elsewhere; editors/agents swapping project directories mid-session; NFS path changes.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/b4db4df5f516d778. Report an issue: GitHub.