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

  1. Stop concurrent processes that mutate the representation tree, then reopen the store
  2. Retry the open if the swap was a one-off race; a clean second attempt succeeds
  3. Serialize namespace mutations with an external lock or the library's coordination mechanism
  4. Check for NFS or network filesystem rename races; move the store to local storage if instability persists
  5. 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

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


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)