{"record":{"id":"afa51027f0acef22","repo":"astrid-runtime/astrid","slug":"representation-directory-changed-while-it-was-open","errorCode":null,"errorMessage":"representation directory changed while it was opened","messagePattern":"representation directory changed while it was opened","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-storage/src/engine/durable/representations/contiguous/namespace.rs","lineNumber":45,"sourceCode":"\npub(in crate::engine::durable) fn open_representation_root(\n    store_root: &Dir,\n) -> Result<Dir, DurableError> {\n    open_component(store_root, Path::new(super::super::DIRECTORY), false)\n}\n\npub(in crate::engine::durable::representations) fn open_component(\n    parent: &Dir,\n    name: &Path,\n    create: bool,\n) -> Result<Dir, DurableError> {\n    let open = || -> io::Result<Dir> {\n        reject_redirect(parent, name, true)?;\n        let first = parent.open_dir(name)?;\n        reject_redirect(parent, name, true)?;\n        let second = parent.open_dir(name)?;\n        if directory_identity(&first)? != directory_identity(&second)? {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                \"representation directory changed while it was opened\",\n            ));\n        }\n        Ok(first)\n    };\n    match open() {\n        Ok(directory) => Ok(directory),\n        Err(error) if create && error.kind() == io::ErrorKind::NotFound => {\n            parent\n                .create_dir(name)\n                .or_else(|source| {\n                    (source.kind() == io::ErrorKind::AlreadyExists)\n                        .then_some(())\n                        .ok_or(source)\n                })\n                .map_err(|source| io_error(\"create representation directory capability\", source))?;\n            sync_directory(parent)","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-storage/src/engine/durable/representations/contiguous/namespace.rs#L27-L63","documentation":"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.","triggerScenarios":"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.","commonSituations":"Concurrent compaction/GC jobs moving representation directories; a crashed recovery process recreating directories; shared NFS volumes where rename swaps race with opens.","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"],"exampleFix":"// before\n(compactor) rename store/rep/x store/rep/x.old; mkdir store/rep/x   # while store open\n// after\npause compaction; open store; resume compaction under lock","handlingStrategy":"retry","validationCode":"if !rep_root.join(name).symlink_metadata()?.is_dir() {\n    return Err(\"namespace entry not a directory\");\n}\nserialize_namespace_mutation();","typeGuard":null,"tryCatchPattern":"match open_representation(path) {\n    Err(e) if e.to_string().contains(\"directory changed while it was opened\") => {\n        pause_background_jobs();\n        open_representation(path) // retry once\n    }\n    r => r,\n}","preventionTips":["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"],"tags":["io","filesystem","race-condition"],"backgroundTag":"file-changed-during-open","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}