nats-io/nats-server · error

sourcing can not be updated

Error message

sourcing can not be updated

What it means

Like Direct, the Sourcing (source/multiple sources) definition classifies a consumer for its whole lifetime and cannot be modified after creation. checkNewConsumerConfig rejects any difference between the existing cfg.Sourcing and the proposed ncfg.Sourcing. This covers both adding sourcing to a plain consumer and altering source streams/subjects.

Source

Thrown at server/consumer.go:2546

// 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) {
			return errors.New("start time can not be updated")
		}
	} else if cfg.OptStartTime != nil || ncfg.OptStartTime != nil {
		// At least one start time is set and the other is not
		return errors.New("start time can not be updated")
	}
	if cfg.AckPolicy != ncfg.AckPolicy {
		return errors.New("ack policy can not be updated")
	}
	if cfg.ReplayPolicy != ncfg.ReplayPolicy {
		return errors.New("replay policy can not be updated")

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Delete and recreate the consumer with the new sourcing configuration.
  2. Create a new consumer with a new durable name pointing at the new sources, then retire the old one.
  3. Revert the sourcing change to match the existing config.
  4. Verify current sources with ConsumerInfo before issuing updates.

Example fix

// before
js.UpdateConsumer(stream, &nats.ConsumerConfig{Durable: "aggregator", Sources: []*nats.ConsumerSource{{Stream: "V2"}}})
// after
js.DeleteConsumer(stream, "aggregator")
js.AddConsumer(stream, &nats.ConsumerConfig{Durable: "aggregator", Sources: []*nats.ConsumerSource{{Stream: "V2"}}})
Defensive patterns

Strategy: validation

Validate before calling

if !reflect.DeepEqual(existing.Sources, desired.Sources) && sourcingChanged(existing, desired) {
	return errors.New("sourcing cannot be updated; delete and recreate the consumer")
}

Prevention

When it happens

Trigger: js.UpdateConsumer on an existing durable where the Source/Source(s) configuration differs — e.g. adding nats.ConsumerSource{Stream: "OTHER"}, changing filter subjects, or removing sources from a sourced consumer.

Common situations: Retargeting a mirror/source to a new stream during a migration; adding a second source for multi-stream aggregation; infra-as-code diffs that mutate the sources block of an existing durable.

Related errors


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