nats-io/nats-server · error · ApiError
first backoff value has to equal batch AckWait
Error message
first backoff value has to equal batch AckWait
What it means
A JetStream pedantic validation error thrown when BackOff is configured but its first element does not equal AckWait. BackOff overrides AckWait (and MaxDeliver), and the invariant is BackOff[0] == AckWait; pedantic mode enforces it instead of silently overwriting AckWait with BackOff[0] at server/consumer.go:680.
Source
Thrown at server/consumer.go:680
if config.PinnedTTL < 0 {
if pedantic {
return NewJSPedanticError(errors.New("priority_timeout must not be negative"))
}
config.PinnedTTL = 0
}
// Set to default if not specified.
if config.DeliverSubject == _EMPTY_ && config.MaxWaiting == 0 {
config.MaxWaiting = JSWaitQueueDefaultMax
}
// Setup proper default for ack wait if we are in explicit ack mode.
if config.AckWait == 0 && (config.AckPolicy == AckExplicit || config.AckPolicy == AckAll) {
config.AckWait = JsAckWaitDefault
}
// If BackOff was specified that will override the AckWait and the MaxDeliver.
if len(config.BackOff) > 0 {
if pedantic && config.AckWait != config.BackOff[0] {
return NewJSPedanticError(errors.New("first backoff value has to equal batch AckWait"))
}
config.AckWait = config.BackOff[0]
}
if config.MaxAckPending == 0 {
if pedantic && streamCfg.ConsumerLimits.MaxAckPending > 0 {
return NewJSPedanticError(errors.New("max_ack_pending must be set if it's configured in stream limits"))
}
config.MaxAckPending = streamCfg.ConsumerLimits.MaxAckPending
}
if config.InactiveThreshold == 0 {
if pedantic && streamCfg.ConsumerLimits.InactiveThreshold > 0 {
return NewJSPedanticError(errors.New("inactive_threshold must be set if it's configured in stream limits"))
}
config.InactiveThreshold = streamCfg.ConsumerLimits.InactiveThreshold
}
// Set proper default for max ack pending if we are ack explicit and none has been set.
if config.MaxAckPending == 0 && config.AckPolicy != AckNone {
ackPending := JsDefaultMaxAckPendingView on GitHub (pinned to 3a66a489d2)
Solutions
- Set AckWait equal to BackOff[0], or simply omit AckWait (0) and let BackOff define it.
- Derive AckWait programmatically: cc.AckWait = cc.BackOff[0] after building the schedule.
- Check tooling/UIs that set AckWait independently from BackOff and keep them in sync.
- Run Validate(true) client-side to catch the mismatch before the server call.
Example fix
// before
cc := nats.ConsumerConfig{
AckWait: 30 * time.Second,
BackOff: []time.Duration{time.Minute, 5 * time.Minute},
}
// after
backoff := []time.Duration{time.Minute, 5 * time.Minute}
cc := nats.ConsumerConfig{
AckWait: backoff[0],
BackOff: backoff,
} Defensive patterns
Strategy: validation
Validate before calling
if len(cfg.BackOff) > 0 && cfg.AckWait != 0 && cfg.AckWait != cfg.BackOff[0] {
return errors.New("AckWait must equal BackOff[0] (or be unset)")
} Try / catch
if _, err := js.AddConsumer(stream, &cc); err != nil && strings.Contains(err.Error(), "first backoff value") {
cc.AckWait = cc.BackOff[0]
_, err = js.AddConsumer(stream, &cc)
} Prevention
- Build backoff schedules in one place and always derive AckWait from BackOff[0].
- When migrating from AckWait+MaxDeliver to BackOff, delete the stale AckWait value.
- Add a unit test asserting AckWait == BackOff[0] for every generated consumer config.
When it happens
Trigger: ConsumerConfig with len(BackOff) > 0, AckWait != 0, and AckWait != BackOff[0], e.g. AckWait: 30*time.Second with BackOff: []time.Duration{1*time.Minute, 5*time.Minute}, submitted with pedantic validation.
Common situations: Teams migrating from plain AckWait+MaxDeliver to exponential BackOff schedules but leaving the old AckWait value; computing backoff arrays programmatically without syncing the first element; older configs that were previously accepted via silent clamping.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- got corrupted escaped character
- max_ack_pending must be set to -1
- max_batch must not be negative
- max_expires must not be negative
- max_bytes must not be negative
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/d401480510bb50c0.
Report an issue: GitHub.