astrid-runtime/astrid · error
workspace capsule portal is not a regular directory: {}
Error message
workspace capsule portal is not a regular directory: {} What it means
The workspace capsule portal (the root passed to collect_workspace_targets) is not a plain directory: it is a symlink or some non-directory entry. The migration barrier refuses to traverse portals that could redirect the walk, so inventory of workspace capsules fails with io::ErrorKind::InvalidData. This is a deliberate anti-symlink safety check, not a lookup failure.
Source
Thrown at crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs:144
),
));
}
}
Ok(())
}
/// Collect capsule directories below a workspace portal without following
/// redirects. The migration barrier uses this inventory when checking that
/// no legacy authority receipts remain attached to a workspace capsule.
pub(super) fn collect_workspace_targets(root: &Path) -> io::Result<Vec<PathBuf>> {
const MAX_WORKSPACE_TARGETS: usize = 4096;
let metadata = match fs::symlink_metadata(root) {
Ok(metadata) => metadata,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(error) => return Err(error),
};
if metadata.file_type().is_symlink() || !metadata.is_dir() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"workspace capsule portal is not a regular directory: {}",
root.display()
),
));
}
astrid_core::platform_fs::verify_no_redirects(root)?;
let mut targets = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
let mut entries = fs::read_dir(&dir)
.map_err(io::Error::other)?
.collect::<Result<Vec<_>, _>>()
.map_err(io::Error::other)?;
entries.sort_by_key(std::fs::DirEntry::file_name);
for entry in entries {
let path = entry.path();View on GitHub (pinned to affd8760f4)
Solutions
- Remove the symlink and create a real directory at the portal path (mv the symlink aside, mkdir, copy contents).
- Point the application's workspace-root configuration at the actual physical directory instead of a symlinked path.
- Check the entry type with `ls -ld <root>`; if it is a file or other special node, move it out of the way and recreate it as a directory.
- Re-run migration; note NotFound is treated as an empty inventory, so ensure the path exists.
Example fix
// before ln -s /data/workspaces ~/.astrid/workspaces // after rm ~/.astrid/workspaces mkdir ~/.astrid/workspaces cp -a /data/workspaces/. ~/.astrid/workspaces/
Defensive patterns
Strategy: validation
Validate before calling
use std::fs;
fn portal_is_regular_dir(root: &std::path::Path) -> std::io::Result<bool> {
match fs::symlink_metadata(root) {
Ok(m) => Ok(!m.file_type().is_symlink() && m.is_dir()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(true), // absent is OK
Err(e) => Err(e),
}
} Type guard
fn is_real_dir(m: &std::fs::Metadata) -> bool {
!m.file_type().is_symlink() && m.is_dir()
} Try / catch
match workspace_portal_targets(root) {
Ok(targets) => targets,
Err(e) if e.kind() == std::io::ErrorKind::InvalidData
&& e.to_string().contains("not a regular directory") =>
{
eprintln!("portal must be a real directory, not a symlink: {e}");
Vec::new()
}
Err(e) => return Err(e),
} Prevention
- Never symlink the workspace portal; if redirection is needed, use the app's own configuration for the path
- After restoring from backups, audit for symlinks: `find ~/.astrid -type l`
- Use archive tools that dereference or preserve directories as directories (tar --dereference)
When it happens
Trigger: Calling collect_workspace_targets (via workspace_portal_targets) when the root path is a symlink to a directory, a regular file, a FIFO/socket/device node, or otherwise fails the symlink_metadata is_dir check. NotFound returns empty instead, so the path must exist to trigger this.
Common situations: Deployments that replaced the workspace portal with a symlink for redirection or NFS mounting; packaging scripts that bind a single capsule file where a directory is expected; restoring the home from an archive tool that materialized symlinks.
Related errors
- inspect projected file {}: {error}
- legacy state source is redirected: {}
- legacy state source contains a redirect: {}
- workspace capsule portal contains a redirect: {}
- directory symlink {} not allowed in capsule source tree (ref
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/683fc080ffd17ac9.
Report an issue: GitHub.