{"record":{"id":"ca7da770d3c70d3c","repo":"nats-io/nats-server","slug":"restore-for-stream-s-s-requires-reply-subjec","errorCode":null,"errorMessage":"restore for stream '%s > %s' requires reply subject for each chunk","messagePattern":"restore for stream '(.+?) > (.+?)' requires reply subject for each chunk","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/jetstream_api.go","lineNumber":4258,"sourceCode":"\t\tif err != nil {\n\t\t\tpr.CloseWithError(err)\n\t\t} else {\n\t\t\tpr.Close()\n\t\t}\n\t\trestoreCh <- struct {\n\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}","sourceCodeStart":4240,"sourceCodeEnd":4276,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/jetstream_api.go#L4240-L4276","documentation":"JetStream stream restore (file-transfer based restore) protocol error. Every chunk published to the restore delivery subject must carry a reply subject so the server can acknowledge and signal failures/flow control. A chunk without one cancels the restore.","triggerScenarios":"Publishing restore chunks to the $JS.API stream restore subject with Request() replaced by plain Publish, or using a client/subscription that strips the reply subject.","commonSituations":"Hand-rolled restore implementations over raw NATS; custom tooling that replays recorded restore traffic without reply subjects; intermediate broker/proxy dropping reply fields.","solutions":["Use nats.Request (request-reply) for each restore chunk instead of Publish","Ensure every chunk publication includes a non-empty reply subject","Prefer the client's built-in restore API (jetStream.StreamsRestore / jsm stream restore) over manual chunking"],"exampleFix":"// before\njs.Publish(restoreSubject, chunk)\n// after\njs.Restore(&nats.StreamConfig{Name: \"S\"}) // client handles per-chunk request/reply\n// or manually:\nresp, err := nc.Request(restoreSubject, chunk, 10*time.Second)","handlingStrategy":"validation","validationCode":"if reply == \"\" {\n\treturn fmt.Errorf(\"restore chunk publish must use request-reply (non-empty reply subject)\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use nc.Request / js restore APIs, never plain Publish for restore chunks","Use the official client restore method instead of hand-rolled transfers"],"tags":["jetstream","stream-restore","protocol","nats-server"],"backgroundTag":"missing-reply-subject","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}