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
- 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
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
- 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
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
- an incomplete capsule authority update exists at {}; remove
- quarantined capsule authority bytes changed: {}
- leftover capsule authority receipt changed before retirement
- capsule {} disappeared during durable contracts scan
- capsule materialization destination is not a directory
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/9bc5522814a86d16.
Report an issue: GitHub.