astrid-runtime/astrid · error

projected file changed while read: {}

Error message

projected file changed while read: {}

What it means

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.

Source

Thrown at crates/astrid-kernel/src/lib.rs:1683

    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
    fn read_projection_file_nofollow(path: &Path) -> anyhow::Result<Vec<u8>> {
        use std::io::Read as _;

        let metadata = std::fs::symlink_metadata(path).map_err(|error| {
            anyhow::anyhow!("inspect projected file {}: {error}", path.display())
        })?;
        if metadata.file_type().is_symlink() || !metadata.is_file() {
            anyhow::bail!(
                "projected path is redirected or not a regular file: {}",
                path.display()
            );
        }
        let mut file = open_projection_file_nofollow(path)?;
        let mut bytes = Vec::new();
        file.read_to_end(&mut bytes)
            .map_err(|error| anyhow::anyhow!("read projected file {}: {error}", path.display()))?;
        if file.metadata()?.len() != metadata.len() || bytes.len() as u64 != metadata.len() {
            anyhow::bail!("projected file changed while read: {}", path.display());
        }
        Ok(bytes)
    }

    /// Load a capsule into the Kernel from a directory containing a Capsule.toml
    ///
    /// # Errors
    ///
    /// Returns an error if the manifest cannot be loaded, the capsule cannot be created, or registration fails.
    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
    async fn load_capsule(
        &self,
        dir: PathBuf,
        principal: &PrincipalId,
    ) -> Result<(), anyhow::Error> {
        self.verify_workspace_capsule_tree(&dir)?;
        let manifest_path = dir.join("Capsule.toml");
        let manifest = astrid_capsule::discovery::load_manifest(&manifest_path)

View on GitHub (pinned to affd8760f4)

Solutions

  1. Re-run the read once the writer has finished (or retry with backoff); the file was likely mid-write
  2. 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
  3. Re-materialize the capsule if its contents were being replaced, then read the fresh projection
  4. If a process legitimately writes into the projection, move those outputs outside the capsule directory

Example fix

// before: single read, no retry on concurrent mutation
let bytes = kernel.read_projected_file(&path)?;

// after: retry once on concurrent-modification failure
let bytes = match kernel.read_projected_file(&path) {
    Ok(b) => b,
    Err(e) if e.to_string().contains("changed while read") => {
        std::thread::sleep(std::time::Duration::from_millis(100));
        kernel.read_projected_file(&path)?
    }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: retry

Validate before calling

let before = std::fs::symlink_metadata(path)?.len();
// after your own read, sanity check: file.metadata().len() == before

Try / catch

match result {
    Err(e) if e.to_string().contains("changed while read") => {
        // wait for the writer to finish, then retry the read
    }
    other => other?,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09). Data as JSON: /api/errors/9bc5522814a86d16. Report an issue: GitHub.