{"record":{"id":"69655c7f863720ea","repo":"AlistGo/alist","slug":"offset-out-of-range","errorCode":null,"errorMessage":"offset out of range","messagePattern":"offset out of range","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/stream/stream.go","lineNumber":432,"sourceCode":"\t\tr.ss.Closers.Add(r.headCache)\n\t}\n}\n\nfunc NewReadAtSeeker(ss *SeekableStream, offset int64, forceRange ...bool) (SStreamReadAtSeeker, error) {\n\tif ss.mFile != nil {\n\t\t_, err := ss.mFile.Seek(offset, io.SeekStart)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &FileReadAtSeeker{ss: ss}, nil\n\t}\n\tr := &RangeReadReadAtSeeker{\n\t\tss:        ss,\n\t\tmasterOff: offset,\n\t}\n\tif offset != 0 || utils.IsBool(forceRange...) {\n\t\tif offset < 0 || offset > ss.GetSize() {\n\t\t\treturn nil, errors.New(\"offset out of range\")\n\t\t}\n\t\t_, err := r.getReaderAtOffset(offset)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\trc := &readerCur{reader: ss, cur: offset}\n\t\tr.readers = append(r.readers, rc)\n\t}\n\treturn r, nil\n}\n\nfunc NewMultiReaderAt(ss []*SeekableStream) (readerutil.SizeReaderAt, error) {\n\treaders := make([]readerutil.SizeReaderAt, 0, len(ss))\n\tfor _, s := range ss {\n\t\tra, err := NewReadAtSeeker(s, 0)\n\t\tif err != nil {\n\t\t\treturn nil, err","sourceCodeStart":414,"sourceCodeEnd":450,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/internal/stream/stream.go#L414-L450","documentation":"When a stream cannot be memory-mapped, RangeReadReadAtSeeker is used and its RangeRead method validates the requested start offset: it must be within [0, size]. An offset beyond the stream size (or negative) returns 'offset out of range' before any network read is attempted. This is a local sanity check, so it fails instantly rather than after a round trip.","triggerScenarios":"Calling the stream's RangeRead/section reader with an offset > ss.GetSize() or < 0 — e.g. a client sending a Range header like 'bytes=999999999999-' on a smaller file, or code computing an offset from stale size metadata.","commonSituations":"Cached file size is smaller than the real file (stale listing) so a valid-looking seek exceeds the cached bound; clients issuing end-anchored ranges computed incorrectly; off-by-one after truncation.","solutions":["Re-validate the offset against the current stream size before reading","Refresh the cached listing/size for the object and retry","Fix client-side range math, especially end-anchored ranges (bytes=-N) converted to absolute offsets"],"exampleFix":"// before\nr, err := ss.RangeRead(offset, length)\n// after\nif offset < 0 || offset > ss.GetSize() {\n    return nil, fmt.Errorf(\"bad offset %d for size %d\", offset, ss.GetSize())\n}\nr, err := ss.RangeRead(offset, length)","handlingStrategy":"validation","validationCode":"// Before a ranged read\nsize := ss.GetSize()\nif offset < 0 || offset > size {\n    return fmt.Errorf(\"offset %d out of range for size %d\", offset, size)\n}","typeGuard":"func validOffset(off int64, size int64) bool {\n    return off >= 0 && off <= size\n}","tryCatchPattern":"r, err := ss.RangeRead(offset, length)\nif err != nil && strings.Contains(err.Error(), \"offset out of range\") {\n    // refresh cached size metadata and recompute the range\n    refreshListing()\n}","preventionTips":["Refresh object size metadata before serving range requests","Clamp client Range headers to the current size","Compute end-anchored ranges (bytes=-N) with care: offset = size - N"],"tags":["stream","range-read","validation"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}