{"record":{"id":"17ef03a481914339","repo":"nats-io/nats-server","slug":"sanity-check-failed","errorCode":null,"errorMessage":"sanity check failed","messagePattern":"sanity check failed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/filestore.go","lineNumber":6496,"sourceCode":"\t}\n\n\tfor index, lbuf := uint32(0), uint32(len(buf)); index < lbuf; {\n\t\tif index+msgHdrSize > lbuf {\n\t\t\treturn fmt.Errorf(\"message overrun\")\n\t\t}\n\t\thdr := buf[index : index+msgHdrSize]\n\t\trl, slen := le.Uint32(hdr[0:]), int(le.Uint16(hdr[20:]))\n\t\thasHeaders := rl&hbit != 0\n\t\t// Clear any headers bit that could be set.\n\t\trl &^= hbit\n\t\tshlen := slen\n\t\tif hasHeaders {\n\t\t\tshlen += 4\n\t\t}\n\t\tdlen := int(rl) - msgHdrSize\n\t\t// Do some quick sanity checks here.\n\t\tif dlen < 0 || shlen > (dlen-recordHashSize) || dlen > int(rl) || index+rl > lbuf || rl > rlBadThresh {\n\t\t\treturn fmt.Errorf(\"sanity check failed\")\n\t\t}\n\t\t// Only need to process non-deleted messages.\n\t\tseq := le.Uint64(hdr[4:])\n\t\tts := int64(le.Uint64(hdr[12:]))\n\n\t\tif !isDeleted(seq) {\n\t\t\t// Check for tombstones.\n\t\t\tif seq&tbit != 0 {\n\t\t\t\tseq = seq &^ tbit\n\t\t\t\t// If this entry is for a lower seq than ours then keep around.\n\t\t\t\t// We also check that it is greater than our floor. Floor is zero on normal\n\t\t\t\t// calls to compact.\n\t\t\t\t// If the global delete map is set, check if a tombstone is still\n\t\t\t\t// referencing a message in another block. If not, it can be removed.\n\t\t\t\tif seq < fseq && seq >= floor && (fsDmap == nil || fsDmap.Exists(seq)) {\n\t\t\t\t\tnbuf = append(nbuf, buf[index:index+rl]...)\n\t\t\t\t}\n\t\t\t} else {","sourceCodeStart":6478,"sourceCodeEnd":6514,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/filestore.go#L6478-L6514","documentation":"Thrown while scanning recovered message block data when a parsed record fails the quick sanity checks: negative data length, computed lengths inconsistent (shlen vs dlen-recordHashSize), record length over the buffer end, or record length beyond rlBadThresh. It guards against interpreting garbage as valid messages after truncation/corruption.","triggerScenarios":"Recovery scanning a block file with corrupted or misaligned record bytes — e.g. bit rot, partial writes, or data written by a mismatched store version — where rl/dlen values are implausible.","commonSituations":"Restore from inconsistent backups, running an older server against newer-format block files (or vice versa), failing disks, manual edits of store files.","solutions":["Reset/recover the affected stream (remove its files and let JetStream recreate) after confirming no healthy copy exists","Restore the store directory from a consistent offline backup","Pin server version across cluster nodes and upgrades so block formats match","Run disk/filesystem health checks to rule out hardware corruption"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Go: detect sanity failures during recovery and fall back to stream reset\nif err := recoverStream(storeDir, streamName); err != nil {\n    if strings.Contains(err.Error(), \"sanity check failed\") {\n        // corrupt block: restore from consistent backup or reset stream\n    }\n    return err\n}","preventionTips":["Keep server versions consistent across upgrades and nodes","Use healthy storage (monitor SMART/fsck) — bit rot triggers this","Restore stores only from backups taken offline","Never hand-edit block files"],"tags":["corruption","recovery","sanity-check","filestore"],"backgroundTag":"message-block-corruption","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}