{"record":{"id":"d5baf7dc38ee88d7","repo":"nats-io/nats-server","slug":"direct-can-not-be-updated","errorCode":null,"errorMessage":"direct can not be updated","messagePattern":"direct can not be updated","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/consumer.go","lineNumber":2543,"sourceCode":"\to.rlimit = rate.NewLimiter(rl, burst)\n}\n\n// Check if new consumer config allowed vs old.\nfunc (acc *Account) checkNewConsumerConfig(cfg, ncfg *ConsumerConfig) error {\n\tif reflect.DeepEqual(cfg, ncfg) {\n\t\treturn nil\n\t}\n\t// Something different, so check since we only allow certain things to be updated.\n\tif cfg.DeliverPolicy != ncfg.DeliverPolicy {\n\t\treturn errors.New(\"deliver policy can not be updated\")\n\t}\n\tif cfg.MemoryStorage != ncfg.MemoryStorage {\n\t\treturn errors.New(\"storage type can not be updated\")\n\t}\n\t// Direct and Sourcing classify the consumer for its whole lifetime, which the\n\t// stream relies on when walking its consumer list, so they can not change.\n\tif cfg.Direct != ncfg.Direct {\n\t\treturn errors.New(\"direct can not be updated\")\n\t}\n\tif cfg.Sourcing != ncfg.Sourcing {\n\t\treturn errors.New(\"sourcing can not be updated\")\n\t}\n\tif cfg.OptStartSeq != ncfg.OptStartSeq {\n\t\treturn errors.New(\"start sequence can not be updated\")\n\t}\n\tif cfg.OptStartTime != nil && ncfg.OptStartTime != nil {\n\t\t// Both have start times set, compare them directly:\n\t\tif !cfg.OptStartTime.Equal(*ncfg.OptStartTime) {\n\t\t\treturn errors.New(\"start time can not be updated\")\n\t\t}\n\t} else if cfg.OptStartTime != nil || ncfg.OptStartTime != nil {\n\t\t// At least one start time is set and the other is not\n\t\treturn errors.New(\"start time can not be updated\")\n\t}\n\tif cfg.AckPolicy != ncfg.AckPolicy {\n\t\treturn errors.New(\"ack policy can not be updated\")","sourceCodeStart":2525,"sourceCodeEnd":2561,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/consumer.go#L2525-L2561","documentation":"The Direct flag classifies a consumer for its entire lifetime; the stream relies on this classification when walking its consumer list, so it can never change. checkNewConsumerConfig rejects the update when cfg.Direct != ncfg.Direct. This typically occurs when toggling between a normal (ack-based) consumer and a direct/ephemeral one under the same durable name.","triggerScenarios":"js.UpdateConsumer on an existing durable where the proposed config's Direct field differs from the stored one (e.g. updating a normal durable to Direct: true, or a direct consumer to Direct: false).","commonSituations":"Migrating an app to direct consumers for read-only replays while reusing old durable names; tooling that always sends the full config flipping the flag; partial upgrades where one service created a direct consumer and another updates it as a normal one.","solutions":["Delete the consumer and recreate it with the desired Direct setting.","Use a distinct durable/name for the direct variant.","Match the existing consumer's Direct value in update requests.","Discover the current config via ConsumerInfo before composing updates."],"exampleFix":"// before\njs.UpdateConsumer(stream, &nats.ConsumerConfig{Durable: \"worker\", Direct: true})\n// after\njs.DeleteConsumer(stream, \"worker\")\njs.AddConsumer(stream, &nats.ConsumerConfig{Durable: \"worker-direct\", Direct: true})","handlingStrategy":"validation","validationCode":"if existing.Direct != desired.Direct {\n\treturn errors.New(\"direct cannot be updated; use a new consumer\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use separate durable names for direct vs normal consumers","Read ConsumerInfo before constructing update requests","Never send full config blindly; diff against current state"],"tags":["jetstream","consumer","direct","immutable-config"],"backgroundTag":"jetstream-consumer-field-immutable","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}