{"record":{"id":"246752ab4753cddd","repo":"nats-io/nats-server","slug":"bad-ack-floor-for-consumer","errorCode":null,"errorMessage":"bad ack floor for consumer","messagePattern":"bad ack floor for consumer","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/filestore.go","lineNumber":13871,"sourceCode":"\tclone.Name = o.name\n\treturn clone\n}\n\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 {","sourceCodeStart":13853,"sourceCodeEnd":13889,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/filestore.go#L13853-L13889","documentation":"Sanity check in (*consumerFileStore).Update: the incoming ConsumerState's AckFloor.Consumer must not exceed Delivered.Consumer. An ack floor ahead of delivered sequences is logically impossible and would corrupt accounting, so the update is rejected. A sibling check enforces the same invariant for the stream sequence.","triggerScenarios":"Calling consumer Update(state) with a ConsumerState where state.AckFloor.Consumer > state.Delivered.Consumer — e.g. loading a state file edited out of order, or a client/replication path writing inconsistent state.","commonSituations":"Hand-edited or partially copied consumer state (msgs.json / JetStream consumer state) files; custom tooling that advances acks without advancing delivered; restore tooling that misassembles ConsumerState.","solutions":["Fix the ConsumerState so AckFloor.Consumer <= Delivered.Consumer before calling Update.","If from a state file, restore the consumer's state file from backup or delete the consumer and recreate it.","Ensure any custom ack-tracking code advances Delivered before/with AckFloor.","Check replication/restore tooling for ordering bugs when assembling state."],"exampleFix":"// before\nstate.AckFloor.Consumer = 150; state.Delivered.Consumer = 120 // invalid\n// after\nstate.AckFloor.Consumer = 120; state.Delivered.Consumer = 150","handlingStrategy":"validation","validationCode":"func stateSane(st *ConsumerState) bool {\n    return st.AckFloor.Consumer <= st.Delivered.Consumer &&\n           st.AckFloor.Stream <= st.Delivered.Stream\n}","typeGuard":"func ackFloorOK(st *ConsumerState) bool {\n    return st != nil && st.AckFloor.Consumer <= st.Delivered.Consumer\n}","tryCatchPattern":"if !stateSane(state) {\n    return errors.New(\"refusing update: ack floor exceeds delivered\")\n}\n// then call Update and handle the error","preventionTips":["Never advance AckFloor beyond Delivered in custom tooling","Do not hand-edit consumer state files","Restore state files as a complete consistent set","Test restore tooling against invariant checks before production use"],"tags":["jetstream","filestore","consumer","state","validation"],"backgroundTag":"bad-ack-floor","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}