nats-io/nats-server · warning

no flow response

Error message

no flow response

What it means

During a JetStream snapshot the receiver must ack each chunk so the server can pace in-flight chunks. If no ack arrives within snapshotAckTimeout the server assumes the receiver stalled or the link is lossy, aborts the snapshot and returns 408 'No Flow Response'.

Source

Thrown at server/jetstream_api.go:4677

		case <-inch:
			// The receiver appears to have gone away.
			snapshotErr = errors.New("no interest")
			hdr = []byte("NATS/1.0 408 No Interest\r\n\r\n")
			goto done
		case err, ok := <-errCh:
			if !ok {
				// Channel closed normally, e.g. on completion.
				errCh = nil
				continue
			}
			// The snapshotting goroutine has failed for some reason.
			snapshotErr = err
			hdr = fmt.Appendf(nil, "NATS/1.0 500 %s\r\n\r\n", err)
			goto done
		case <-ackTimer.C:
			// It's taking a very long time for the receiver to send us acks,
			// they have probably stalled or there is high loss on the link.
			snapshotErr = errors.New("no flow response")
			hdr = []byte("NATS/1.0 408 No Flow Response\r\n\r\n")
			goto done
		}
		n, err := io.ReadFull(r, chunk)
		chunk := chunk[:n]
		if err != nil {
			if n > 0 {
				mset.outq.send(newJSPubMsg(reply, _EMPTY_, _EMPTY_, nil, chunk, nil, 0))
			}
			select {
			case err, ok := <-errCh:
				if ok {
					snapshotErr = err
					hdr = fmt.Appendf(nil, "NATS/1.0 500 %s\r\n\r\n", err)
				}
			default:
			}
			break

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Fix the receiver so it acks every chunk promptly (drain the snapshot reader continuously).
  2. Retry the snapshot on a stable, low-latency connection after repairing the link.
  3. Reduce per-snapshot size (snapshot more often) or tune chunking for the network path.
  4. Check receiver health (CPU, memory, blocking I/O) before retrying.

Example fix

// before: receiver stalls doing heavy work between reads, acks stop
for chunk := range snapshotChunks {
	heavyBlockingTransform(chunk) // -> 408 no flow response
}
// after: keep draining so acks keep flowing
for chunk := range snapshotChunks {
	select {
	case sink <- chunk:
	case <-ctx.Done():
		return ctx.Err()
	}
}
Defensive patterns

Strategy: retry

Validate before calling

// ensure the receiver can drain and ack before starting
nc.SetErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) {
	log.Printf("nats error during snapshot: %v", err)
})

Type guard

func isNoFlowResponse(err error) bool {
	return err != nil && strings.Contains(err.Error(), "no flow response")
}

Try / catch

err := snapshotStream(js, "ORDERS", w)
if isNoFlowResponse(err) {
	if receiverHealthy() {
		return snapshotStream(js, "ORDERS", w) // stall was transient
	}
	return fmt.Errorf("receiver stalled during snapshot: %w", err)
}

Prevention

When it happens

Trigger: The receiving client stops processing chunks (blocked consumer, slow sink, GC pause) without sending acks; ack messages are lost after a connection interruption; snapshotAckTimeout elapses between chunk sends on a large/slow snapshot.

Common situations: Backing up a very large stream over a high-latency/lossy link; overloaded or paused receiver process; flow-control acks dropped after NATS client reconnect; undersized reader buffers stalling the ack loop.

Related errors


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