AlistGo/alist · error

invalid seek: negative position

Error message

invalid seek: negative position

What it means

RangeReadReadAtSeeker.Seek computes the resulting absolute offset for SeekStart/SeekCurrent/SeekEnd and rejects negative results with 'invalid seek: negative position', returning the unchanged current offset. Seeking past the end returns io.EOF instead. The check protects underlying range readers from negative index panics.

Source

Thrown at internal/stream/stream.go:543

	}
	return num, err
}

func (r *RangeReadReadAtSeeker) Seek(offset int64, whence int) (int64, error) {
	switch whence {
	case io.SeekStart:
	case io.SeekCurrent:
		if offset == 0 {
			return r.masterOff, nil
		}
		offset += r.masterOff
	case io.SeekEnd:
		offset += r.ss.GetSize()
	default:
		return 0, errs.NotSupport
	}
	if offset < 0 {
		return r.masterOff, errors.New("invalid seek: negative position")
	}
	if offset > r.ss.GetSize() {
		return r.masterOff, io.EOF
	}
	r.masterOff = offset
	return offset, nil
}

func (r *RangeReadReadAtSeeker) Read(p []byte) (n int, err error) {
	if r.masterOff == 0 && r.headCache != nil {
		return r.headCache.read(p)
	}
	rc, err := r.getReaderAtOffset(r.masterOff)
	if err != nil {
		return 0, err
	}
	n, err = rc.reader.Read(p)
	rc.cur += int64(n)

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Clamp seek targets to [0, size] before calling Seek
  2. Fix delta computation for SeekCurrent (use absolute SeekStart when possible)
  3. Check the returned offset after a failed seek — it remains the pre-seek position, so state stays consistent

Example fix

// before
newPos, err := r.Seek(want-current, io.SeekCurrent)
// after: seek by absolute position
if want < 0 {
    want = 0
}
newPos, err := r.Seek(want, io.SeekStart)
Defensive patterns

Strategy: validation

Validate before calling

// Compute absolute target then clamp
abs := target
switch whence {
case io.SeekCurrent: abs = r.masterOff + delta
case io.SeekEnd: abs = r.ss.GetSize() + delta
}
if abs < 0 { abs = 0 }

Try / catch

if _, err := r.Seek(delta, io.SeekCurrent); err != nil {
    if strings.Contains(err.Error(), "negative position") {
        pos, _ := r.Seek(0, io.SeekStart) // recover to a known position
    }
}

Prevention

When it happens

Trigger: Seek(-n, io.SeekStart); or Seek with a negative delta under io.SeekCurrent that crosses zero; or Seek(-n, io.SeekEnd) where n > file size.

Common situations: HTTP handler rewinding by a chunk under SeekCurrent; resumable-download code computing skip = pos - want (negative when already past the target); end-anchored parsing logic seeking backwards past position 0.

Related errors


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/5961bcee1807dc07. Report an issue: GitHub.