thanos-io/thanos · error

new TOC

Error message

new TOC

What it means

The TOC bytes were fetched and the reader closed, but index.NewTOCFromByteSlice failed while parsing the TOC structure (or validating its CRC32). This means the final bytes of the index file are not a well-formed table of contents, so the index-header cannot be built.

Solutions

  1. Verify the index object size matches attrs.Size and re-upload the block if the object is truncated.
  2. Delete the corrupt block (or remove it from the bucket) so store/gateway stops loading it; restore from a replica.
  3. If size mismatch came from cached metadata, invalidate the cached attributes and re-fetch.
  4. Run a block integrity check (e.g. thanos tools bucket verify) to identify and quarantine corrupt blocks.
Defensive patterns

Strategy: validation

Validate before calling

attrs, _ := bkt.Attributes(ctx, path)
if attrs.Size < int64(indexTOCLen+crc32.Size) {
    return fmt.Errorf("index %s truncated: %d bytes", path, attrs.Size)
}

Prevention

When it happens

Trigger: newChunkedIndexReader -> readTOC when NewTOCFromByteSlice rejects the tocBytes: wrong TOC offset (r.size from attrs does not match the actual object), truncated index object, or a structurally invalid TOC written by a broken writer.

Common situations: Index object truncated mid-upload so the TOC tail is missing; stale attrs.Size after the object was replaced with a different-sized one; blocks corrupted during copy between buckets; CRC mismatch from bit rot.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/91cbdd74b1392c1f. Report an issue: GitHub.

Appendix: source

Thrown at pkg/block/indexheader/binary_reader.go:247

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")
	}

	return nil
}

View on GitHub (pinned to 35b8b99117)