sinelaw/fresh · error
read_range: expected
Error message
read_range: expected {} bytes at offset {}, got {} (agent reported: {:?}, path: {}) What it means
The remote filesystem adapter's read_range requested `len` bytes at `offset` from the remote agent, but the response payload contained a different number of bytes (short or long read). Mirrors LocalFileSystem::read_exact semantics: short reads indicate truncation, races, or stale metadata, so the mismatch is surfaced with the agent-reported length and path for diagnosis.
Solutions
- Re-stat the file on the remote and retry read_range with fresh metadata (invalidate the cached size).
- Check whether the file is being modified concurrently; re-open the file handle or re-read the whole file.
- Verify remote agent and editor protocol versions match; upgrade the agent if range semantics differ.
Example fix
// before
let bytes = remote_fs.read_range(&path, offset, len)?;
// after
let bytes = match remote_fs.read_range(&path, offset, len) {
Ok(b) => b,
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
remote_fs.refresh_metadata(&path)?; // re-stat, drop cached size
remote_fs.read_range(&path, offset, remote_fs.metadata(&path)?.len())?
}
Err(e) => return Err(e),
}; Defensive patterns
Strategy: retry
Validate before calling
let stat_len = remote_fs.metadata(&path)?.len();
if offset + len as u64 > stat_len { return Err("range exceeds current file size".into()); } Type guard
fn range_in_bounds(meta_len: u64, offset: u64, len: usize) -> bool { offset.saturating_add(len as u64) <= meta_len } Try / catch
let bytes = loop {
match remote_fs.read_range(&path, offset, len) {
Ok(b) => break b,
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => {
remote_fs.refresh_metadata(&path)?;
remote_fs.read_range(&path, offset, len)
}
Err(e) => break Err(e),
}
}; Prevention
- Re-stat remote files before ranged reads; never trust cached sizes across long intervals
- Detect concurrent modification (mtime/size change) and re-open the file
- Keep remote agent and editor protocol versions in sync
When it happens
Trigger: read_range called on a remote file that shrank (truncate/rewrite) between stat and read, a stale cached file size from the agent, or the remote agent returning fewer/more bytes than requested for the given range.
Common situations: Another process appending or rewriting the file during editing, remote file mounted over an unreliable network share, or an agent/protocol version that reports sizes differently (stale metadata cache).
Related errors
AI-assisted analysis of sinelaw/fresh@67894ca546 (2026-09-13).
Data as JSON: /api/errors/eade7c30a2f96af8.
Report an issue: GitHub.
Appendix: source
Thrown at crates/fresh-editor/src/services/remote/filesystem.rs:277
for chunk in data_chunks {
if let Some(b64) = chunk.get("data").and_then(|v| v.as_str()) {
let decoded = decode_base64(b64)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
content.extend(decoded);
}
}
// Get the size reported by the agent (how many bytes it actually read from the file)
let agent_reported_size = result
.get("size")
.and_then(|v| v.as_u64())
.map(|s| s as usize);
// Validate that we received the expected number of bytes.
// This matches LocalFileSystem::read_range which uses read_exact.
// Short reads indicate file truncation, race conditions, or metadata mismatch.
if content.len() != len {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
format!(
"read_range: expected {} bytes at offset {}, got {} (agent reported: {:?}, path: {})",
len,
offset,
content.len(),
agent_reported_size,
path_str
),
));
}
Ok(content)
}
fn count_line_feeds_in_range(&self, path: &Path, offset: u64, len: usize) -> io::Result<usize> {
let path_str = path.to_string_lossy();
let result = selfView on GitHub (pinned to 67894ca546)