thanos-io/thanos · error
close toc reader
Error message
close toc reader
What it means
After successfully reading the TOC bytes, readTOC closes the range-read body. If rc.Close() returns an error (some backends validate checksums or finalize uploads on Close), the read is abandoned with this 'close toc reader' error rather than returning a possibly unverifiable TOC.
Solutions
- Look at the wrapped Close error: if it is checksum validation, treat the read as failed and retry the fetch.
- If a custom bucket implementation is in use, ensure Close only performs cleanup or surfaces genuine transfer errors.
- Retry the whole readTOC; transient body-close errors resolve on re-fetch.
- Check objstore client versions for known SDK close/validation bugs and upgrade.
Defensive patterns
Strategy: retry
Try / catch
err := readTOC(ctx)
var closeErr interface{ Unwrap() error }
if errors.As(err, &closeErr) && isChecksumError(errors.Unwrap(err)) {
return retry(readTOC)
} Prevention
- Keep objstore SDKs up to date to avoid close-time validation bugs.
- If using custom bucket wrappers, ensure Close only reports genuine transfer errors.
- Treat any close-stage checksum error as a full-read failure and refetch.
When it happens
Trigger: newChunkedIndexReader -> readTOC where the HTTP/body Close call after a successful ReadAll fails — e.g. S3 client-side CRC/validation error at body finalization, or custom objstore bucket implementations returning errors from Close.
Common situations: Custom objstore wrappers that do work in Close and fail; corrupted transfer detected at body close by SDK checksum validation; resource cleanup failures in exotic backends.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/f82ca7491858987f.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/block/indexheader/binary_reader.go:242
ir.toc = toc
return ir, version, nil
}
func (r *chunkedIndexReader) readTOC() (*index.TOC, error) {
rc, err := r.bkt.GetRange(r.ctx, r.path, int64(r.size-indexTOCLen-crc32.Size), indexTOCLen+crc32.Size)
if err != nil {
return nil, errors.Wrapf(err, "get TOC from object storage of %s", r.path)
}
tocBytes, err := io.ReadAll(rc)
if err != nil {
runutil.CloseWithErrCapture(&err, rc, "close toc reader")
return nil, errors.Wrapf(err, "get TOC from object storage of %s", r.path)
}
if err := rc.Close(); err != nil {
return nil, errors.Wrap(err, "close toc reader")
}
toc, err := index.NewTOCFromByteSlice(realByteSlice(tocBytes))
if err != nil {
return nil, errors.Wrap(err, "new TOC")
}
return toc, nil
}
func (r *chunkedIndexReader) CopySymbols(w io.Writer, buf []byte) (err error) {
rc, err := r.bkt.GetRange(r.ctx, r.path, int64(r.toc.Symbols), int64(r.toc.Series-r.toc.Symbols))
if err != nil {
return errors.Wrapf(err, "get symbols from object storage of %s", r.path)
}
defer runutil.CloseWithErrCapture(&err, rc, "close symbol reader")
if _, err := io.CopyBuffer(w, rc, buf); err != nil {
return errors.Wrap(err, "copy symbols")View on GitHub (pinned to 35b8b99117)