{"record":{"id":"5961bcee1807dc07","repo":"AlistGo/alist","slug":"invalid-seek-negative-position","errorCode":null,"errorMessage":"invalid seek: negative position","messagePattern":"invalid seek: negative position","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/stream/stream.go","lineNumber":543,"sourceCode":"\t}\n\treturn num, err\n}\n\nfunc (r *RangeReadReadAtSeeker) Seek(offset int64, whence int) (int64, error) {\n\tswitch whence {\n\tcase io.SeekStart:\n\tcase io.SeekCurrent:\n\t\tif offset == 0 {\n\t\t\treturn r.masterOff, nil\n\t\t}\n\t\toffset += r.masterOff\n\tcase io.SeekEnd:\n\t\toffset += r.ss.GetSize()\n\tdefault:\n\t\treturn 0, errs.NotSupport\n\t}\n\tif offset < 0 {\n\t\treturn r.masterOff, errors.New(\"invalid seek: negative position\")\n\t}\n\tif offset > r.ss.GetSize() {\n\t\treturn r.masterOff, io.EOF\n\t}\n\tr.masterOff = offset\n\treturn offset, nil\n}\n\nfunc (r *RangeReadReadAtSeeker) Read(p []byte) (n int, err error) {\n\tif r.masterOff == 0 && r.headCache != nil {\n\t\treturn r.headCache.read(p)\n\t}\n\trc, err := r.getReaderAtOffset(r.masterOff)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\tn, err = rc.reader.Read(p)\n\trc.cur += int64(n)","sourceCodeStart":525,"sourceCodeEnd":561,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/internal/stream/stream.go#L525-L561","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Clamp seek targets to [0, size] before calling Seek","Fix delta computation for SeekCurrent (use absolute SeekStart when possible)","Check the returned offset after a failed seek — it remains the pre-seek position, so state stays consistent"],"exampleFix":"// before\nnewPos, err := r.Seek(want-current, io.SeekCurrent)\n// after: seek by absolute position\nif want < 0 {\n    want = 0\n}\nnewPos, err := r.Seek(want, io.SeekStart)","handlingStrategy":"validation","validationCode":"// Compute absolute target then clamp\nabs := target\nswitch whence {\ncase io.SeekCurrent: abs = r.masterOff + delta\ncase io.SeekEnd: abs = r.ss.GetSize() + delta\n}\nif abs < 0 { abs = 0 }","typeGuard":null,"tryCatchPattern":"if _, err := r.Seek(delta, io.SeekCurrent); err != nil {\n    if strings.Contains(err.Error(), \"negative position\") {\n        pos, _ := r.Seek(0, io.SeekStart) // recover to a known position\n    }\n}","preventionTips":["Prefer absolute SeekStart over relative SeekCurrent in rewind logic","Clamp all computed seek targets to [0, size]","Remember the returned offset is the pre-seek position after this error"],"tags":["stream","seek","validation"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}