{"record":{"id":"1f7ed52787240fc3","repo":"nats-io/nats-server","slug":"change-to-limits-violates-consumers-s","errorCode":null,"errorMessage":"change to limits violates consumers: %s","messagePattern":"change to limits violates consumers: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/stream.go","lineNumber":2696,"sourceCode":"\t\tmset.mu.RLock()\n\t\tclist := make([]*consumer, 0, len(mset.consumers))\n\t\tfor _, c := range mset.consumers {\n\t\t\tclist = append(clist, c)\n\t\t}\n\t\tmset.mu.RUnlock()\n\t\tfor _, c := range clist {\n\t\t\tc.mu.RLock()\n\t\t\tname, ccfg := c.name, c.cfg\n\t\t\tif (newInactiveThreshold > 0 && ccfg.InactiveThreshold > newInactiveThreshold) ||\n\t\t\t\t(newMaxAckPending > 0 && ccfg.MaxAckPending > newMaxAckPending) {\n\t\t\t\terrorConsumers = append(errorConsumers, name)\n\t\t\t}\n\t\t\tc.mu.RUnlock()\n\t\t}\n\t\tif len(errorConsumers) > 0 {\n\t\t\t// TODO(nat): Return a parsable error so that we can surface something\n\t\t\t// sensible through the JS API.\n\t\t\treturn fmt.Errorf(\"change to limits violates consumers: %s\", strings.Join(errorConsumers, \", \"))\n\t\t}\n\t}\n\n\tjsa.mu.RLock()\n\tif jsa.subjectsOverlap(cfg.Subjects, mset) {\n\t\tjsa.mu.RUnlock()\n\t\treturn NewJSStreamSubjectOverlapError()\n\t}\n\tjsa.mu.RUnlock()\n\n\tmset.mu.Lock()\n\n\tvar needsStartingSeqNum map[string]struct{}\n\tif mset.active {\n\t\t// Check for mirror promotion.\n\t\tif ocfg.Mirror != nil && cfg.Mirror == nil {\n\t\t\t// Only try deleting the sourcing consumer if one wasn't provided to us.\n\t\t\tif ocfg.Mirror.Consumer == nil {","sourceCodeStart":2678,"sourceCodeEnd":2714,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/stream.go#L2678-L2714","documentation":"When updating stream limits (MaxMsgs, MaxBytes, MaxAge, MaxMsgSize, MaxMsgsPerSubject etc.), JetStream checks every consumer against the new limits; if any consumer's state would be violated (e.g. its redelivery/ack floor exceeds the new retention), the update is rejected listing the offending consumers. As the TODO notes, this is currently a plain error without a parsable JS API error code.","triggerScenarios":"UpdateStream with tighter limits while consumers exist whose progress (e.g. consumer sequence, pending state) is beyond what the new limits permit; also triggered when subject overlaps are checked right after this path.","commonSituations":"Reducing MaxAge or MaxMsgs to clean up disk while long-lived pull consumers still track old messages; shrinking MaxBytes below current consumer state after storage growth incidents.","solutions":["Identify the consumers named in the message and delete or recreate them before tightening limits.","Loosen the limits so all existing consumers remain valid, then tighten after consumers have caught up.","Drain/consumer-delete the affected consumers, apply the update, and re-create consumers with fresh DeliverPolicy."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"for _, cl := range consumerList {\n    ci, _ := js.ConsumerInfo(ctx, stream, cl)\n    if newCfg.MaxAge > 0 && time.Since(ci.Delivered.Last) > newCfg.MaxAge {\n        // tighten limits after deleting/draining this consumer\n    }\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"violates consumers\") { /* parse consumer names after 'violates consumers: ' */ }","preventionTips":["Delete or drain consumers before shrinking MaxMsgs/MaxAge/MaxBytes.","Tighten limits in stages while consumers catch up."],"tags":["jetstream","stream-config","consumers","limits"],"backgroundTag":"stream-limits-violate-consumers","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}