{"record":{"id":"8c7999ce89c56cac","repo":"nats-io/nats-server","slug":"error-reading-compression-reader-w","errorCode":null,"errorMessage":"error reading compression reader: %w","messagePattern":"error reading compression reader: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/filestore.go","lineNumber":14561,"sourceCode":"\tbodyLen := int64(len(buf) - checksumSize)\n\tinput := bytes.NewReader(buf[:bodyLen])\n\n\tvar reader io.ReadCloser\n\tswitch alg {\n\tcase NoCompression:\n\t\treturn buf, nil\n\tcase S2Compression:\n\t\treader = io.NopCloser(s2.NewReader(input))\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"compression algorithm not known\")\n\t}\n\n\t// Decompress the block content. The checksum isn't compressed so\n\t// we can preserve it from the end of the block as-is.\n\tchecksum := buf[bodyLen:]\n\toutput, err := io.ReadAll(reader)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading compression reader: %w\", err)\n\t}\n\toutput = append(output, checksum...)\n\n\treturn output, reader.Close()\n}\n\n// writeFileWithOptionalSync is equivalent to os.WriteFile() but optionally\n// sets O_SYNC on the open file if SyncAlways is set. The dios semaphore is\n// handled automatically by this function, so don't wrap calls to it in dios.\nfunc (fs *fileStore) writeFileWithOptionalSync(name string, data []byte, perm fs.FileMode) error {\n\tsync := fs.syncAlways.Load() || fs.syncOnFlush.Load()\n\treturn writeAtomically(fs.dios, name, data, perm, sync)\n}\n\nfunc writeFileWithSync(dios *diskIOSemaphore, name string, data []byte, perm fs.FileMode) error {\n\treturn writeAtomically(dios, name, data, perm, true)\n}\n","sourceCodeStart":14543,"sourceCodeEnd":14579,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/filestore.go#L14543-L14579","documentation":"Returned when io.ReadAll(reader) fails while draining the decompressed block content from the s2 reader. The s2 decoding error (corrupt stream, unexpected EOF) is wrapped with %w so the original cause is preserved.","triggerScenarios":"io.ReadAll on the s2 decompression reader errors because the compressed body bytes are corrupt or truncated (e.g. stored block damaged on disk, wrong slice boundaries passed to Decompress).","commonSituations":"Disk corruption or bit rot in filestore block files, a crash mid-write leaving a partial compressed body, checksum not catching damage before decompression, or decompressing a buffer sliced incorrectly (off-by-one cutting the body).","solutions":["Verify the stored block's integrity (file size vs expected bodyLen + checksumSize) and re-fetch/re-sync the block","Check disk health (SMART) and filesystem logs for corruption","Confirm the buffer passed to Decompress includes the full body plus trailing checksum","Upgrade NATS Server in case of a known decompression bug; restore affected blocks from backup"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if len(buf) <= checksumSize { return errors.New(\"no compressed body to read\") }","typeGuard":"func isS2Corrupt(err error) bool { return err != nil && strings.Contains(err.Error(), \"error reading compression reader\") }","tryCatchPattern":"out, err := alg.Decompress(buf)\nif err != nil {\n    if strings.Contains(err.Error(), \"error reading compression reader\") {\n        // corrupted/truncated s2 stream: restore block from backup or resync\n    }\n    return err\n}","preventionTips":["Enable fsync/durability so blocks are not half-written on crash","Run filesystem checks after unclean shutdowns; watch for disk errors","Keep backups/replicas of filestore blocks; verify sizes before decompressing"],"tags":["compression","s2","data-corruption"],"backgroundTag":"decompression-failed","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}