{"record":{"id":"9bc5522814a86d16","repo":"astrid-runtime/astrid","slug":"projected-file-changed-while-read","errorCode":null,"errorMessage":"projected file changed while read: {}","messagePattern":"projected file changed while read: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/lib.rs","lineNumber":1683,"sourceCode":"    #[cfg(not(all(target_arch = \"wasm32\", target_os = \"unknown\")))]\n    fn read_projection_file_nofollow(path: &Path) -> anyhow::Result<Vec<u8>> {\n        use std::io::Read as _;\n\n        let metadata = std::fs::symlink_metadata(path).map_err(|error| {\n            anyhow::anyhow!(\"inspect projected file {}: {error}\", path.display())\n        })?;\n        if metadata.file_type().is_symlink() || !metadata.is_file() {\n            anyhow::bail!(\n                \"projected path is redirected or not a regular file: {}\",\n                path.display()\n            );\n        }\n        let mut file = open_projection_file_nofollow(path)?;\n        let mut bytes = Vec::new();\n        file.read_to_end(&mut bytes)\n            .map_err(|error| anyhow::anyhow!(\"read projected file {}: {error}\", path.display()))?;\n        if file.metadata()?.len() != metadata.len() || bytes.len() as u64 != metadata.len() {\n            anyhow::bail!(\"projected file changed while read: {}\", path.display());\n        }\n        Ok(bytes)\n    }\n\n    /// Load a capsule into the Kernel from a directory containing a Capsule.toml\n    ///\n    /// # Errors\n    ///\n    /// Returns an error if the manifest cannot be loaded, the capsule cannot be created, or registration fails.\n    #[cfg(not(all(target_arch = \"wasm32\", target_os = \"unknown\")))]\n    async fn load_capsule(\n        &self,\n        dir: PathBuf,\n        principal: &PrincipalId,\n    ) -> Result<(), anyhow::Error> {\n        self.verify_workspace_capsule_tree(&dir)?;\n        let manifest_path = dir.join(\"Capsule.toml\");\n        let manifest = astrid_capsule::discovery::load_manifest(&manifest_path)","sourceCodeStart":1665,"sourceCodeEnd":1701,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/lib.rs#L1665-L1701","documentation":"This error means a projected file's size changed between the initial symlink_metadata stat and the completion of read_to_end — either the post-read metadata length or the byte count read differs from the original stat. The kernel throws it to guarantee snapshot consistency: a file that mutates while being read could yield a torn mix of old and new content, which would silently corrupt integrity verification.","triggerScenarios":"Reading a projected file when the file is concurrently written/truncated/appended during read_to_end — detected because file.metadata()?.len() != metadata.len() or bytes.len() as u64 != metadata.len(). Typical when something rewrites the capsule directory (re-materialization, another kernel instance) mid-read.","commonSituations":"Two kernel processes sharing one capsule directory with one re-materializing while the other reads; a deploy pipeline overwriting capsule files during a running inspection; log-style writers appending to a file inside the projection; editors/build tools touching output files during verification.","solutions":["Re-run the read once the writer has finished (or retry with backoff); the file was likely mid-write","Ensure exclusive access: don't re-materialize or write into a capsule directory while it is being read/inventoried; use a lock or per-process capsule dirs","Re-materialize the capsule if its contents were being replaced, then read the fresh projection","If a process legitimately writes into the projection, move those outputs outside the capsule directory"],"exampleFix":"// before: single read, no retry on concurrent mutation\nlet bytes = kernel.read_projected_file(&path)?;\n\n// after: retry once on concurrent-modification failure\nlet bytes = match kernel.read_projected_file(&path) {\n    Ok(b) => b,\n    Err(e) if e.to_string().contains(\"changed while read\") => {\n        std::thread::sleep(std::time::Duration::from_millis(100));\n        kernel.read_projected_file(&path)?\n    }\n    Err(e) => return Err(e),\n};","handlingStrategy":"retry","validationCode":"let before = std::fs::symlink_metadata(path)?.len();\n// after your own read, sanity check: file.metadata().len() == before","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"changed while read\") => {\n        // wait for the writer to finish, then retry the read\n    }\n    other => other?,\n}","preventionTips":["Do not re-materialize or write into a capsule directory while reading it","Give each kernel process its own capsule directory","Retry transient mid-write failures with backoff","Move log/app outputs outside the capsule projection"],"tags":["concurrency","filesystem","consistency","capsule","retryable"],"backgroundTag":"invalid-state-transition","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}