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:
}
breakView on GitHub (pinned to 3a66a489d2)
Solutions
- Fix the receiver so it acks every chunk promptly (drain the snapshot reader continuously).
- Retry the snapshot on a stable, low-latency connection after repairing the link.
- Reduce per-snapshot size (snapshot more often) or tune chunking for the network path.
- 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
- Drain snapshot chunks continuously; never block between reads on the receiving side.
- Watch receiver CPU/GC pauses that can exceed the ack deadline.
- Snapshot large streams over low-loss links or in smaller pieces.
- Alert on reconnects/subscription loss during backups.
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
- no interest
- group node unhealthy
- flow control ack policy heartbeat needs to be 1s
- snapshot check failed: %w
- snapshot check detected %d bad messages
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/780e810d8d076ad1.
Report an issue: GitHub.