{"record":{"id":"b8e56c0938b5c16c","repo":"thanos-io/thanos","slug":"read-got-d-bytes-instead-of-4","errorCode":null,"errorMessage":"read got %d bytes instead of 4","messagePattern":"read got (.+?) bytes instead of 4","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/store/postings.go","lineNumber":57,"sourceCode":"\t\tr:                r,\n\t\treadBuf:          make([]byte, 4),\n\t\tstart:            start,\n\t\tlength:           length,\n\t\tpostings:         postings,\n\t\tuvarintEncodeBuf: make([]byte, binary.MaxVarintLen64),\n\t\tctx:              ctx,\n\t}\n\n\treturn prb\n}\n\nfunc getInt32(r io.Reader, buf []byte) (uint32, error) {\n\tread, err := io.ReadFull(r, buf)\n\tif err != nil {\n\t\treturn 0, errors.Wrap(err, \"reading\")\n\t}\n\tif read != 4 {\n\t\treturn 0, fmt.Errorf(\"read got %d bytes instead of 4\", read)\n\t}\n\treturn binary.BigEndian.Uint32(buf), nil\n}\n\nfunc (r *postingsReaderBuilder) Next() bool {\n\tif r.ctx.Err() != nil {\n\t\tr.e = r.ctx.Err()\n\t\treturn false\n\t}\n\tif r.repeatFor > 0 {\n\t\tr.keyID = r.postings[r.pi-r.repeatFor].keyID\n\t\tr.repeatFor--\n\t\treturn true\n\t}\n\tif r.pi >= len(r.postings) {\n\t\treturn false\n\t}\n\tif r.Error() != nil {","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/pkg/store/postings.go#L39-L75","documentation":"getInt32 also returns fmt.Errorf(\"read got %d bytes instead of 4\") when io.ReadFull returned successfully but the count differs — a defensive check that should only trigger for readers behaving inconsistently (short reads without proper error). It indicates malformed postings binary data.","triggerScenarios":"A reader implementation returning fewer than 4 bytes without returning an error from Read, corrupting the postings count decoding in postingsReaderBuilder.Next.","commonSituations":"Custom/wrapped io.Reader implementations with buggy Read semantics; corrupted mmap regions where ReadFull semantics degrade; desynchronized offsets in hand-crafted postings streams.","solutions":["Fix or replace the io.Reader wrapper violating the io.Reader contract (short read without EOF error).","Verify the postings data offsets are in sync — a corrupted stream likely desynchronized the decoder.","Regenerate/re-download the block index data.","Add a buffered reader (bufio) to normalize read behavior over the underlying source."],"exampleFix":"// before\nr := myCustomReader(raw)\n// after\nr := bufio.NewReader(io.MultiReader(bytes.NewReader(header), raw))","handlingStrategy":"try-catch","validationCode":"// ensure underlying reader honors io.Reader contract\nvar probe [4]byte\nif n, err := io.ReadFull(reader, probe[:]); err != nil || n != 4 { return err }","typeGuard":null,"tryCatchPattern":"if err != nil {\n  var serr interface{ ShortRead() bool } // or match on message\n  if strings.Contains(err.Error(), \"instead of 4\") {\n    // reader contract violation: replace reader or resync stream offsets\n  }\n}","preventionTips":["Wrap raw sources in bufio.Reader to normalize short reads","Test custom io.Reader implementations against io.ReadFull","Never reuse a postings reader after an error without resyncing"],"tags":["go","io","index","corruption"],"backgroundTag":"unexpected-response-shape","analyzedSha":"35b8b991177def87ed52dcf10f9b6d87f07282c8","analyzedAt":"2026-09-07T01:49:59.689Z","contentChangedAt":"2026-09-07T01:49:59.689Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}