{"record":{"id":"00057c65d3c2faf7","repo":"nats-io/nats-server","slug":"short-write-on-checksum-d-d","errorCode":null,"errorMessage":"short write on checksum (%d != %d)","messagePattern":"short write on checksum \\((.+?) != (.+?)\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/filestore.go","lineNumber":14533,"sourceCode":"\tinput := bytes.NewReader(buf[:bodyLen])\n\tchecksum := buf[bodyLen:]\n\n\t// Compress the block content, but don't compress the checksum.\n\t// We will preserve it at the end of the block as-is.\n\tif n, err := io.CopyN(writer, input, bodyLen); err != nil {\n\t\treturn nil, fmt.Errorf(\"error writing to compression writer: %w\", err)\n\t} else if n != bodyLen {\n\t\treturn nil, fmt.Errorf(\"short write on body (%d != %d)\", n, bodyLen)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\treturn nil, fmt.Errorf(\"error closing compression writer: %w\", err)\n\t}\n\n\t// Now add the checksum back onto the end of the block.\n\tif n, err := output.Write(checksum); err != nil {\n\t\treturn nil, fmt.Errorf(\"error writing checksum: %w\", err)\n\t} else if n != checksumSize {\n\t\treturn nil, fmt.Errorf(\"short write on checksum (%d != %d)\", n, checksumSize)\n\t}\n\n\treturn output.Bytes(), nil\n}\n\nfunc (alg StoreCompression) Decompress(buf []byte) ([]byte, error) {\n\tif len(buf) < checksumSize {\n\t\treturn nil, fmt.Errorf(\"compressed buffer is too short\")\n\t}\n\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))","sourceCodeStart":14515,"sourceCodeEnd":14551,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/filestore.go#L14515-L14551","documentation":"Returned when output.Write(checksum) succeeds but writes fewer bytes than checksumSize, indicating a short write. A truncated checksum would make the block undetectably corrupt on read, so the library rejects it.","triggerScenarios":"The output writer accepts the checksum slice but reports n != checksumSize during StoreCompression block compression.","commonSituations":"Custom io.Writer implementations that legally return short writes without error (io.Writer contract permits this) — most standard writers never do this.","solutions":["If output is a custom writer, make Write loop until the full slice is consumed or return an error","Use bytes.Buffer or another writer that always writes fully for the checksum append","Treat this as a bug in the output writer; replace it with a stdlib buffer"],"exampleFix":"// before (custom writer may short-write)\noutput = myWriter{}\n// after\noutput = bytes.NewBuffer(make([]byte, 0, bodyLen+checksumSize))","handlingStrategy":"validation","validationCode":"if cw, ok := output.(interface{ Write([]byte) (int, error) }); ok { if n, err := cw.Write([]byte(\"x\")); err != nil || n != 1 { /* writer may short-write */ } }","typeGuard":"func writesFully(w io.Writer) bool { b := bytes.NewBuffer(nil); n, err := w.Write(make([]byte, 16)); return err == nil && n == 16 && (b == nil) }","tryCatchPattern":"_, err := alg.Compress(body)\nif err != nil && strings.Contains(err.Error(), \"short write on checksum\") {\n    // replace custom writer with bytes.Buffer and retry\n}","preventionTips":["Never pass custom io.Writer implementations that short-write without error","Prefer bytes.Buffer as the compression output target","Wrap any custom writer with a write-full loop adapter"],"tags":["io","checksum","short-write"],"backgroundTag":"io-short-write","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}