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

  1. Re-stat the file on the remote and retry read_range with fresh metadata (invalidate the cached size).
  2. Check whether the file is being modified concurrently; re-open the file handle or re-read the whole file.
  3. 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

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 = self

View on GitHub (pinned to 67894ca546)