nats-io/nats-server · error

restore for stream '%s > %s' received short chunk

Error message

restore for stream '%s > %s' received short chunk

What it means

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.

Source

Thrown at server/jetstream_api.go:4264

			mset *stream
			err  error
		}{
			mset: mset,
			err:  err,
		}
	})

	processChunk := func(sub *subscription, c *client, _ *Account, subject, reply string, msg []byte) {
		// We require reply subjects to communicate back failures, flow etc. If they do not have one log and cancel.
		if reply == _EMPTY_ {
			sub.client.processUnsub(sub.sid)
			setResult(fmt.Errorf("restore for stream '%s > %s' requires reply subject for each chunk", acc.Name, streamName), reply)
			return
		}
		// Account client messages have \r\n on end. This is an error.
		if len(msg) < LEN_CR_LF {
			sub.client.processUnsub(sub.sid)
			setResult(fmt.Errorf("restore for stream '%s > %s' received short chunk", acc.Name, streamName), reply)
			return
		}
		// Adjust.
		msg = msg[:len(msg)-LEN_CR_LF]

		// This means we are complete with our transfer from the client.
		if len(msg) == 0 {
			s.Debugf("Finished streaming restore for stream '%s > %s'", acc.Name, streamName)
			closeWithError(nil)
			setResult(nil, reply)
			return
		}

		// Signal activity before and after the blocking write.
		// The pre-write signal refreshes the stall watchdog when the
		// chunk arrives; the post-write signal refreshes it again once
		// RestoreStream has consumed the data. This keeps the idle
		// window between chunks anchored to the end of the previous

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Send raw bytes with a trailing \r\n as the NATS wire protocol requires
  2. Ensure chunk boundaries align with the byte ranges the server granted, never sending empty chunks
  3. Use the official client restore API which handles framing correctly

Example fix

// before
nc.Request(subj, chunk[:len(chunk)-2], timeout) // stripped CRLF
// after
nc.Request(subj, chunk, timeout) // chunk framed as received, CRLF intact
Defensive patterns

Strategy: validation

Validate before calling

if len(chunk) < 2 || chunk[len(chunk)-2] != '\r' || chunk[len(chunk)-1] != '\n' {
	return fmt.Errorf("restore chunk missing CRLF terminator")
}

Type guard

func chunkWellFormed(chunk []byte) bool {
	return len(chunk) >= 2 && chunk[len(chunk)-2] == '\r' && chunk[len(chunk)-1] == '\n'
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/5c2ff940eb0af5b6. Report an issue: GitHub.