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

  1. Take a lock or use atomic-rename replacement so a file is never mutated in place during a read
  2. Retry the whole read/replace cycle after the concurrent writer finishes
  3. Verify file length before and after reading and treat mismatch as retryable
  4. 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

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


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)