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
- Re-create the WorkspaceSelection (call WorkspaceSelection::resolve again) and retry the operation with the fresh selection.
- Do not cache a selection across workspace moves; re-resolve whenever the project root may have changed.
- Check for concurrent processes moving or re-mounting the workspace and serialize such maintenance with library usage.
- 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
- Re-create the WorkspaceSelection after any project move, re-checkout, or remount
- Don't cache selections across long-running sessions that outlive workspace changes
- Serialize workspace moves/renames with library usage (locks, shutdown hooks)
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
- Workspace capsule directory changed during manifest lookup:
- PermissionDenied
- workspace descendant must be a non-empty relative path witho
- durable capsule {id} has unsafe WIT metadata path {relative}
- legacy capsule {id} changed before retirement
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/b4db4df5f516d778.
Report an issue: GitHub.