astrid-runtime/astrid · error · io::Error
representation directory changed while it was opened
Error message
representation directory changed while it was opened
What it means
open_component in the contiguous-representation namespace opens a directory twice and compares stable identities; a mismatch means the directory entry changed between the opens. The library throws this InvalidData error to prevent operating on a swapped representation directory. It is the representation-layer analogue of the principal-store identity check.
Solutions
- Stop concurrent processes that mutate the representation tree, then reopen the store
- Retry the open if the swap was a one-off race; a clean second attempt succeeds
- Serialize namespace mutations with an external lock or the library's coordination mechanism
- Check for NFS or network filesystem rename races; move the store to local storage if instability persists
- Restore the representation tree from backup if directories were genuinely replaced
Example fix
// before (compactor) rename store/rep/x store/rep/x.old; mkdir store/rep/x # while store open // after pause compaction; open store; resume compaction under lock
Defensive patterns
Strategy: retry
Validate before calling
if !rep_root.join(name).symlink_metadata()?.is_dir() {
return Err("namespace entry not a directory");
}
serialize_namespace_mutation(); Try / catch
match open_representation(path) {
Err(e) if e.to_string().contains("directory changed while it was opened") => {
pause_background_jobs();
open_representation(path) // retry once
}
r => r,
} Prevention
- Do not run compaction/GC that renames representation directories concurrently with opens
- Coordinate namespace mutations via the library's locking
- Avoid NFS/network filesystems for the representation tree
- Re-open the store after any maintenance operation completes
When it happens
Trigger: Calling open, activate, or open_representation_root while another process deletes/recreates or renames the representation directory mid-open, so first and second handles resolve to different inodes.
Common situations: Concurrent compaction/GC jobs moving representation directories; a crashed recovery process recreating directories; shared NFS volumes where rename swaps race with opens.
Related errors
- layout migration destination changed type
- layout migration destination changed while inventoried
- legacy source changed while opening
- legacy state source changed type
- opened representation entry is redirected or not a regular…
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/afa51027f0acef22.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-storage/src/engine/durable/representations/contiguous/namespace.rs:45
pub(in crate::engine::durable) fn open_representation_root(
store_root: &Dir,
) -> Result<Dir, DurableError> {
open_component(store_root, Path::new(super::super::DIRECTORY), false)
}
pub(in crate::engine::durable::representations) fn open_component(
parent: &Dir,
name: &Path,
create: bool,
) -> Result<Dir, DurableError> {
let open = || -> io::Result<Dir> {
reject_redirect(parent, name, true)?;
let first = parent.open_dir(name)?;
reject_redirect(parent, name, true)?;
let second = parent.open_dir(name)?;
if directory_identity(&first)? != directory_identity(&second)? {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"representation directory changed while it was opened",
));
}
Ok(first)
};
match open() {
Ok(directory) => Ok(directory),
Err(error) if create && error.kind() == io::ErrorKind::NotFound => {
parent
.create_dir(name)
.or_else(|source| {
(source.kind() == io::ErrorKind::AlreadyExists)
.then_some(())
.ok_or(source)
})
.map_err(|source| io_error("create representation directory capability", source))?;
sync_directory(parent)View on GitHub (pinned to affd8760f4)