astrid-runtime/astrid · error
mounted file changed during replacement
Error message
mounted file changed during replacement
What it means
The mounted-file reader reads a requested count of bytes from the backing filesystem and verifies it got exactly that many. A short read means the mounted file shrank or was replaced concurrently while it was being read for a replacement operation, so the read is aborted with UnexpectedEof rather than returning torn data.
Solutions
- Take a lock or use atomic-rename replacement so a file is never mutated in place during a read
- Retry the whole read/replace cycle after the concurrent writer finishes
- Verify file length before and after reading and treat mismatch as retryable
- Coordinate writers so only one replacement runs at a time per mounted path
Example fix
// before
let bytes = fs.read(path, 0, len)?;
apply(bytes);
// after
let lock = mount.lock_file(path)?;
let bytes = fs.read(path, 0, len)?;
if bytes.len() != len { return Err(retry); }
apply(bytes);
drop(lock); Defensive patterns
Strategy: retry
Validate before calling
fn file_is_stable(fs: &Mount, path: &Path, expected_len: u64) -> io::Result<bool> {
let before = fs.len(path)?;
std::thread::sleep(std::time::Duration::from_millis(5));
Ok(before == expected_len && fs.len(path)? == expected_len)
} Try / catch
match result {
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof
&& e.to_string() == "mounted file changed during replacement" => {
// re-read the file's current length and retry the replacement
retry_with_backoff(3, || replace_mounted_file(path));
}
other => other?,
} Prevention
- Serialize replacements per mounted path with a lock or single-writer discipline
- Use write-to-temp + atomic rename instead of in-place truncation
- Re-stat file length before and after reading to detect concurrent changes
When it happens
Trigger: During a read() on a mounted file, self.filesystem.read(self.path, self.position, count) returns fewer than count bytes — the file was truncated/rewritten underneath the in-progress replacement.
Common situations: Two writers replace the same mounted file concurrently; an external process truncates the file mid-read; a snapshot/replacement pipeline racing with a live edit.
Related errors
- inspect capsule projection
- leftover capsule authority receipt changed before retirement
- legacy component source reappeared during ordinary…
- legacy source changed before retirement
- projected file changed while read
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/df2a54bbbfb4b90a.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-kernel/src/storage_mount/filesystem/range.rs:80
} else {
let boundary = if self.position < self.offset {
self.offset
} else {
self.new_length
};
let count = count
.min(usize::try_from(boundary.strict_sub(self.position)).unwrap_or(usize::MAX));
if self.position < self.old_length {
let count = count.min(
usize::try_from(self.old_length.strict_sub(self.position))
.unwrap_or(usize::MAX),
);
let bytes = self
.filesystem
.read(self.path, self.position, count as u64)
.map_err(|error| std::io::Error::other(error.to_string()))?;
if bytes.len() != count {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"mounted file changed during replacement",
));
}
output[..count].copy_from_slice(&bytes);
count
} else {
output[..count].fill(0);
count
}
};
self.position = self.position.strict_add(count as u64);
Ok(count)
}
}
#[cfg(test)]
mod tests {View on GitHub (pinned to affd8760f4)