clockworklabs/SpacetimeDB · error · std::io::Error

invalid seek to a negative or overflowing position

Error message

invalid seek to a negative or overflowing position

What it means

The in-memory Segment implements io::Seek with std::io::Cursor semantics. SeekFrom::Start is always allowed (even past the end), but a relative seek (SeekFrom::Current or SeekFrom::End) whose signed offset would move the position below zero, or overflow u64, fails base_pos.checked_add_signed(offset) and returns io::ErrorKind::InvalidInput with this message.

Source

Thrown at crates/commitlog/src/repo/mem/segment.rs:167

    }
}

impl io::Seek for Segment {
    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
        let (base_pos, offset) = match pos {
            io::SeekFrom::Start(n) => {
                self.pos = n;
                return Ok(n);
            }
            io::SeekFrom::End(n) => (self.len() as u64, n),
            io::SeekFrom::Current(n) => (self.pos, n),
        };
        match base_pos.checked_add_signed(offset) {
            Some(n) => {
                self.pos = n;
                Ok(n)
            }
            None => Err(io::Error::new(
                io::ErrorKind::InvalidInput,
                "invalid seek to a negative or overflowing position",
            )),
        }
    }
}

impl SegmentLen for Segment {
    fn segment_len(&mut self) -> io::Result<u64> {
        Ok(self.len() as _)
    }
}

impl FileLike for Segment {
    fn fsync(&mut self) -> io::Result<()> {
        Ok(())
    }

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Compute the absolute target first (stream_position()/len() + checked_add_signed) and clamp to 0 before issuing SeekFrom::Start
  2. Guard relative seeks: only seek End(-n)/Current(-n) when n <= len()/stream_position()
  3. Use u64 arithmetic and SeekFrom::Start instead of signed Current/End offsets

Example fix

// before
segment.seek(io::SeekFrom::Current(-(header_len as i64)))?;

// after
let pos = segment.stream_position()?;
let target = pos.saturating_sub(header_len as u64);
segment.seek(io::SeekFrom::Start(target))?;
Defensive patterns

Strategy: validation

Validate before calling

let pos = segment.stream_position()?;
let len = segment.seek(io::SeekFrom::End(0))?;
// compute the target absolutely, clamped to the segment bounds
let target = (pos as i64 + delta).clamp(0, len as i64) as u64;
segment.seek(io::SeekFrom::Start(target))?;

Type guard

fn is_invalid_seek(err: &io::Error) -> bool {
    err.kind() == io::ErrorKind::InvalidInput
        && err.to_string().contains("negative or overflowing position")
}

Try / catch

let new_pos = match segment.seek(rel) {
    Ok(p) => p,
    Err(e) if is_invalid_seek(&e) => {
        // recover by clamping to start
        segment.seek(io::SeekFrom::Start(0))?
    }
    Err(e) => return Err(e),
};

Prevention

When it happens

Trigger: seek(SeekFrom::End(-n)) with n greater than the segment length; seek(SeekFrom::Current(-n)) with n greater than the current position; an offset computed via i64 subtraction that underflows (e.g. pos - extra as i64 when extra > pos).

Common situations: Rewind-by-N logic after reading variable-length records; subtracting a header/checksum size from a position that is smaller than it; porting seek code that assumed clamping instead of erroring.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/06988d4d694045c3. Report an issue: GitHub.