nats-io/nats-server · error
deliver policy can not be updated
Error message
deliver policy can not be updated
What it means
checkNewConsumerConfig compares the existing consumer config with the proposed update and rejects changes to immutable fields. DeliverPolicy (DeliverAll, DeliverLast, DeliverByStartSequence, DeliverByStartTime, etc.) cannot change after a durable consumer is created because it determines where the consumer begins consuming. The update request fails with this error.
Source
Thrown at server/consumer.go:2535
acc.mu.RUnlock()
if mpay > 0 {
burst = int(mpay)
} else {
burst = int(acc.srv.getOpts().MaxPayload)
}
}
o.rlimit = rate.NewLimiter(rl, burst)
}
// Check if new consumer config allowed vs old.
func (acc *Account) checkNewConsumerConfig(cfg, ncfg *ConsumerConfig) error {
if reflect.DeepEqual(cfg, ncfg) {
return nil
}
// Something different, so check since we only allow certain things to be updated.
if cfg.DeliverPolicy != ncfg.DeliverPolicy {
return errors.New("deliver policy can not be updated")
}
if cfg.MemoryStorage != ncfg.MemoryStorage {
return errors.New("storage type can not be updated")
}
// Direct and Sourcing classify the consumer for its whole lifetime, which the
// stream relies on when walking its consumer list, so they can not change.
if cfg.Direct != ncfg.Direct {
return errors.New("direct can not be updated")
}
if cfg.Sourcing != ncfg.Sourcing {
return errors.New("sourcing can not be updated")
}
if cfg.OptStartSeq != ncfg.OptStartSeq {
return errors.New("start sequence can not be updated")
}
if cfg.OptStartTime != nil && ncfg.OptStartTime != nil {
// Both have start times set, compare them directly:
if !cfg.OptStartTime.Equal(*ncfg.OptStartTime) {View on GitHub (pinned to 3a66a489d2)
Solutions
- Delete the consumer (js.DeleteConsumer / $JS.API.CONSUMER.DELETE) and recreate it with the desired DeliverPolicy.
- Use a new durable name for the new policy and migrate consumers to it.
- Revert the config change so DeliverPolicy matches the existing consumer.
- Read the existing config first (js.ConsumerInfo) and only send fields you intend to change.
Example fix
// before
js.UpdateConsumer(stream, &nats.ConsumerConfig{Durable: "worker", DeliverPolicy: nats.DeliverLast})
// after
js.DeleteConsumer(stream, "worker")
js.AddConsumer(stream, &nats.ConsumerConfig{Durable: "worker", DeliverPolicy: nats.DeliverLast}) Defensive patterns
Strategy: validation
Validate before calling
func safeUpdate(existing, desired *nats.ConsumerConfig) (*nats.ConsumerConfig, error) {
if existing.DeliverPolicy != desired.DeliverPolicy {
return nil, fmt.Errorf("deliver policy cannot be updated; delete and recreate consumer %q", existing.Durable)
}
return desired, nil
} Prevention
- Fetch ConsumerInfo and diff before updating
- Treat DeliverPolicy as create-only in infrastructure code
- On intentional policy change, delete + recreate via automation
When it happens
Trigger: Calling js.UpdateConsumer or the $JS.API.CONSUMER.CREATE API with an existing durable name but a different DeliverPolicy than the stored config.
Common situations: Changing an app from DeliverLast to DeliverNew during redeployment; reusing an old durable name with different start semantics after a refactor; CI applying config drift from a template with a different deliver policy.
Related errors
- storage type can not be updated
- direct can not be updated
- sourcing can not be updated
- start sequence can not be updated
- start time can not be updated
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/5d06debc06e1c9cf.
Report an issue: GitHub.