thanos-io/thanos · error

reading

Error message

reading

What it means

getInt32 reads exactly 4 bytes from an index postings reader; io.ReadFull failures (EOF, closed reader) are wrapped as "reading". It signals the postings binary stream ended prematurely or the underlying reader broke while decoding a posting count.

Solutions

  1. Re-download/repair the block index (delete the cached partial file and re-sync).
  2. Verify block integrity with thanos tools bucket verify.
  3. Check that the underlying reader/stream is not closed while reading.
  4. Inspect the wrapped error (io.EOF vs io.ErrUnexpectedEOF vs syscall errors) for the exact cause.

Example fix

// before
// reuse of possibly corrupt cached index
// after
os.Remove(cacheIndexFile) // force re-download and re-verify checksum
store.SyncBlocks(ctx)
Defensive patterns

Strategy: fallback

Validate before calling

// validate index integrity before decoding
if err := verifyIndexChecksum(indexPath); err != nil { reDownloadIndex() }

Try / catch

if err != nil && strings.Contains(err.Error(), "reading") {
  if errors.Is(errors.Unwrap(err), io.EOF) || errors.Is(errors.Unwrap(err), io.ErrUnexpectedEOF) {
    // treat as corrupt/truncated index: re-fetch block
  }
}

Prevention

When it happens

Trigger: postingsReaderBuilder.Next reading from a truncated/corrupted postings file, a closed connection/stream, or an EOF at the end of malformed data.

Common situations: Corrupted block index files in object storage; interrupted downloads leaving partial index data; reading a buffer/stream that was closed early.

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/1b18b0fda74b4e0a. Report an issue: GitHub.

Appendix: source

Thrown at pkg/store/postings.go:54

// and builds a diff varint encoded []byte that could be later used directly.
func newPostingsReaderBuilder(ctx context.Context, r *bufio.Reader, postings []postingPtr, start, length int64) *postingsReaderBuilder {
	prb := &postingsReaderBuilder{
		r:                r,
		readBuf:          make([]byte, 4),
		start:            start,
		length:           length,
		postings:         postings,
		uvarintEncodeBuf: make([]byte, binary.MaxVarintLen64),
		ctx:              ctx,
	}

	return prb
}

func getInt32(r io.Reader, buf []byte) (uint32, error) {
	read, err := io.ReadFull(r, buf)
	if err != nil {
		return 0, errors.Wrap(err, "reading")
	}
	if read != 4 {
		return 0, fmt.Errorf("read got %d bytes instead of 4", read)
	}
	return binary.BigEndian.Uint32(buf), nil
}

func (r *postingsReaderBuilder) Next() bool {
	if r.ctx.Err() != nil {
		r.e = r.ctx.Err()
		return false
	}
	if r.repeatFor > 0 {
		r.keyID = r.postings[r.pi-r.repeatFor].keyID
		r.repeatFor--
		return true
	}
	if r.pi >= len(r.postings) {

View on GitHub (pinned to 35b8b99117)