{"record":{"id":"d401480510bb50c0","repo":"nats-io/nats-server","slug":"first-backoff-value-has-to-equal-batch-ackwait","errorCode":null,"errorMessage":"first backoff value has to equal batch AckWait","messagePattern":"first backoff value has to equal batch AckWait","errorType":"validation","errorClass":"ApiError","httpStatus":null,"severity":"error","filePath":"server/consumer.go","lineNumber":680,"sourceCode":"\tif config.PinnedTTL < 0 {\n\t\tif pedantic {\n\t\t\treturn NewJSPedanticError(errors.New(\"priority_timeout must not be negative\"))\n\t\t}\n\t\tconfig.PinnedTTL = 0\n\t}\n\n\t// Set to default if not specified.\n\tif config.DeliverSubject == _EMPTY_ && config.MaxWaiting == 0 {\n\t\tconfig.MaxWaiting = JSWaitQueueDefaultMax\n\t}\n\t// Setup proper default for ack wait if we are in explicit ack mode.\n\tif config.AckWait == 0 && (config.AckPolicy == AckExplicit || config.AckPolicy == AckAll) {\n\t\tconfig.AckWait = JsAckWaitDefault\n\t}\n\t// If BackOff was specified that will override the AckWait and the MaxDeliver.\n\tif len(config.BackOff) > 0 {\n\t\tif pedantic && config.AckWait != config.BackOff[0] {\n\t\t\treturn NewJSPedanticError(errors.New(\"first backoff value has to equal batch AckWait\"))\n\t\t}\n\t\tconfig.AckWait = config.BackOff[0]\n\t}\n\tif config.MaxAckPending == 0 {\n\t\tif pedantic && streamCfg.ConsumerLimits.MaxAckPending > 0 {\n\t\t\treturn NewJSPedanticError(errors.New(\"max_ack_pending must be set if it's configured in stream limits\"))\n\t\t}\n\t\tconfig.MaxAckPending = streamCfg.ConsumerLimits.MaxAckPending\n\t}\n\tif config.InactiveThreshold == 0 {\n\t\tif pedantic && streamCfg.ConsumerLimits.InactiveThreshold > 0 {\n\t\t\treturn NewJSPedanticError(errors.New(\"inactive_threshold must be set if it's configured in stream limits\"))\n\t\t}\n\t\tconfig.InactiveThreshold = streamCfg.ConsumerLimits.InactiveThreshold\n\t}\n\t// Set proper default for max ack pending if we are ack explicit and none has been set.\n\tif config.MaxAckPending == 0 && config.AckPolicy != AckNone {\n\t\tackPending := JsDefaultMaxAckPending","sourceCodeStart":662,"sourceCodeEnd":698,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/consumer.go#L662-L698","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ncc := nats.ConsumerConfig{\n    AckWait: 30 * time.Second,\n    BackOff: []time.Duration{time.Minute, 5 * time.Minute},\n}\n// after\nbackoff := []time.Duration{time.Minute, 5 * time.Minute}\ncc := nats.ConsumerConfig{\n    AckWait: backoff[0],\n    BackOff: backoff,\n}","handlingStrategy":"validation","validationCode":"if len(cfg.BackOff) > 0 && cfg.AckWait != 0 && cfg.AckWait != cfg.BackOff[0] {\n    return errors.New(\"AckWait must equal BackOff[0] (or be unset)\")\n}","typeGuard":null,"tryCatchPattern":"if _, err := js.AddConsumer(stream, &cc); err != nil && strings.Contains(err.Error(), \"first backoff value\") {\n    cc.AckWait = cc.BackOff[0]\n    _, err = js.AddConsumer(stream, &cc)\n}","preventionTips":["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."],"tags":["jetstream","consumer-config","backoff","redelivery"],"backgroundTag":"invalid-argument-value","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}