{"record":{"id":"5c2ff940eb0af5b6","repo":"nats-io/nats-server","slug":"restore-for-stream-s-s-received-short-chunk","errorCode":null,"errorMessage":"restore for stream '%s > %s' received short chunk","messagePattern":"restore for stream '(.+?) > (.+?)' received short chunk","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/jetstream_api.go","lineNumber":4264,"sourceCode":"\t\t\tmset *stream\n\t\t\terr  error\n\t\t}{\n\t\t\tmset: mset,\n\t\t\terr:  err,\n\t\t}\n\t})\n\n\tprocessChunk := func(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) {\n\t\t// We require reply subjects to communicate back failures, flow etc. If they do not have one log and cancel.\n\t\tif reply == _EMPTY_ {\n\t\t\tsub.client.processUnsub(sub.sid)\n\t\t\tsetResult(fmt.Errorf(\"restore for stream '%s > %s' requires reply subject for each chunk\", acc.Name, streamName), reply)\n\t\t\treturn\n\t\t}\n\t\t// Account client messages have \\r\\n on end. This is an error.\n\t\tif len(msg) < LEN_CR_LF {\n\t\t\tsub.client.processUnsub(sub.sid)\n\t\t\tsetResult(fmt.Errorf(\"restore for stream '%s > %s' received short chunk\", acc.Name, streamName), reply)\n\t\t\treturn\n\t\t}\n\t\t// Adjust.\n\t\tmsg = msg[:len(msg)-LEN_CR_LF]\n\n\t\t// This means we are complete with our transfer from the client.\n\t\tif len(msg) == 0 {\n\t\t\ts.Debugf(\"Finished streaming restore for stream '%s > %s'\", acc.Name, streamName)\n\t\t\tcloseWithError(nil)\n\t\t\tsetResult(nil, reply)\n\t\t\treturn\n\t\t}\n\n\t\t// Signal activity before and after the blocking write.\n\t\t// The pre-write signal refreshes the stall watchdog when the\n\t\t// chunk arrives; the post-write signal refreshes it again once\n\t\t// RestoreStream has consumed the data. This keeps the idle\n\t\t// window between chunks anchored to the end of the previous","sourceCodeStart":4246,"sourceCodeEnd":4282,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/jetstream_api.go#L4246-L4282","documentation":"JetStream stream restore chunk validation error. Restore chunks are published as normal NATS messages, which must end with CRLF (\\r\\n); a message smaller than the CRLF length (or otherwise missing the terminator) is treated as a short/invalid chunk and the restore is cancelled.","triggerScenarios":"Publishing a zero-length or truncated chunk to the restore subject; a chunk whose trailing \\r\\n was stripped by intermediate processing or custom serialization.","commonSituations":"Hand-rolled restore tools that split data and trim line endings; sending the final zero-byte 'done' payload incorrectly; corruption when replaying captured traffic through a transformer.","solutions":["Send raw bytes with a trailing \\r\\n as the NATS wire protocol requires","Ensure chunk boundaries align with the byte ranges the server granted, never sending empty chunks","Use the official client restore API which handles framing correctly"],"exampleFix":"// before\nnc.Request(subj, chunk[:len(chunk)-2], timeout) // stripped CRLF\n// after\nnc.Request(subj, chunk, timeout) // chunk framed as received, CRLF intact","handlingStrategy":"validation","validationCode":"if len(chunk) < 2 || chunk[len(chunk)-2] != '\\r' || chunk[len(chunk)-1] != '\\n' {\n\treturn fmt.Errorf(\"restore chunk missing CRLF terminator\")\n}","typeGuard":"func chunkWellFormed(chunk []byte) bool {\n\treturn len(chunk) >= 2 && chunk[len(chunk)-2] == '\\r' && chunk[len(chunk)-1] == '\\n'\n}","tryCatchPattern":null,"preventionTips":["Never trim or transform chunk bytes between read and publish","Verify chunk sizes against the server-granted byte ranges"],"tags":["jetstream","stream-restore","protocol","nats-server"],"backgroundTag":"restore-chunk-short","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}