{"record":{"id":"dc1256ecf7a65b30","repo":"nats-io/nats-server","slug":"bad-ack-floor-for-stream","errorCode":null,"errorMessage":"bad ack floor for stream","messagePattern":"bad ack floor for stream","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/filestore.go","lineNumber":13874,"sourceCode":"\nfunc (o *consumerFileStore) UpdateConfig(cfg *ConsumerConfig) error {\n\to.mu.Lock()\n\tdefer o.mu.Unlock()\n\n\t// This is mostly unchecked here. We are assuming the upper layers have done sanity checking.\n\tcsi := o.cfg\n\tcsi.ConsumerConfig = *cfg\n\n\treturn o.writeConsumerMeta()\n}\n\nfunc (o *consumerFileStore) Update(state *ConsumerState) error {\n\t// Sanity checks.\n\tif state.AckFloor.Consumer > state.Delivered.Consumer {\n\t\treturn fmt.Errorf(\"bad ack floor for consumer\")\n\t}\n\tif state.AckFloor.Stream > state.Delivered.Stream {\n\t\treturn fmt.Errorf(\"bad ack floor for stream\")\n\t}\n\n\t// Copy to our state.\n\tvar pending map[uint64]*Pending\n\tvar redelivered map[uint64]uint64\n\tif len(state.Pending) > 0 {\n\t\tpending = make(map[uint64]*Pending, len(state.Pending))\n\t\tfor seq, p := range state.Pending {\n\t\t\tpending[seq] = &Pending{p.Sequence, p.Timestamp}\n\t\t\tif seq <= state.AckFloor.Stream || seq > state.Delivered.Stream {\n\t\t\t\treturn fmt.Errorf(\"bad pending entry, sequence [%d] out of range\", seq)\n\t\t\t}\n\t\t}\n\t}\n\tif len(state.Redelivered) > 0 {\n\t\tredelivered = make(map[uint64]uint64, len(state.Redelivered))\n\t\tfor seq, dc := range state.Redelivered {\n\t\t\tredelivered[seq] = dc","sourceCodeStart":13856,"sourceCodeEnd":13892,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/filestore.go#L13856-L13892","documentation":"consumerFileStore.Update() validates that a consumer's ack floor never exceeds the last delivered sequence. If state.AckFloor.Stream > state.Delivered.Stream, the incoming ConsumerState is internally inconsistent — messages cannot have been acknowledged past the point they were delivered. This is a sanity check in the JetStream file-based consumer state store (server/filestore.go).","triggerScenarios":"Calling Update(state *ConsumerState) with a state where AckFloor.Stream is greater than Delivered.Stream, typically from corrupted state, a restored snapshot from an older/rolled-back stream, or application code that advances the ack floor without advancing delivered.","commonSituations":"Restoring a consumer from a state file captured at a different point in time than the stream; manual state migration between servers; a bug in an operator tool that fabricates ConsumerState; crash-recovery mixing old ack state with new delivered state.","solutions":["Check state.AckFloor.Stream vs state.Delivered.Stream before calling Update and correct or reject the state","Use ForceUpdate() instead if the regression is intentional during recovery (it skips the backwards check)","Regenerate the consumer state from the actual stream contents rather than trusting the imported snapshot","Verify the stream's delivered sequence hasn't been reset (e.g. stream recreated with lower sequence)"],"exampleFix":"// before\nif err := store.Update(&badState); err != nil { ... } // bad ack floor for stream\n// after\nif badState.AckFloor.Stream > badState.Delivered.Stream {\n\tbadState.AckFloor = badState.Delivered\n}\nif err := store.Update(&badState); err != nil { ... }","handlingStrategy":"validation","validationCode":"func validAckFloor(s *ConsumerState) bool {\n\treturn s.AckFloor.Stream <= s.Delivered.Stream && s.AckFloor.Consumer <= s.Delivered.Consumer\n}\nif !validAckFloor(st) { return fmt.Errorf(\"refusing Update: ack floor ahead of delivered\") }","typeGuard":"func stateIsConsistent(s *ConsumerState) bool {\n\treturn s != nil && s.AckFloor.Stream <= s.Delivered.Stream\n}","tryCatchPattern":"if err := store.Update(st); err != nil {\n\tif strings.Contains(err.Error(), \"bad ack floor\") {\n\t\t// rebuild state from stream instead of retrying\n\t}\n\treturn err\n}","preventionTips":["Always derive ack floor and delivered from the same snapshot","Never hand-edit ConsumerState; use documented recovery APIs","Validate imported state against current stream sequence before Update","Prefer ForceUpdate only for deliberate rollback, with logging"],"tags":["jetstream","consumer-state","filestore","validation"],"backgroundTag":"consumer-state-inconsistent","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}