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

  1. Look at the wrapped Close error: if it is checksum validation, treat the read as failed and retry the fetch.
  2. If a custom bucket implementation is in use, ensure Close only performs cleanup or surfaces genuine transfer errors.
  3. Retry the whole readTOC; transient body-close errors resolve on re-fetch.
  4. 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

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)