nats-io/nats-server · error
bad ack floor for stream
Error message
bad ack floor for stream
What it means
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).
Source
Thrown at server/filestore.go:13874
func (o *consumerFileStore) UpdateConfig(cfg *ConsumerConfig) error {
o.mu.Lock()
defer o.mu.Unlock()
// This is mostly unchecked here. We are assuming the upper layers have done sanity checking.
csi := o.cfg
csi.ConsumerConfig = *cfg
return o.writeConsumerMeta()
}
func (o *consumerFileStore) Update(state *ConsumerState) error {
// Sanity checks.
if state.AckFloor.Consumer > state.Delivered.Consumer {
return fmt.Errorf("bad ack floor for consumer")
}
if state.AckFloor.Stream > state.Delivered.Stream {
return fmt.Errorf("bad ack floor for stream")
}
// Copy to our state.
var pending map[uint64]*Pending
var redelivered map[uint64]uint64
if len(state.Pending) > 0 {
pending = make(map[uint64]*Pending, len(state.Pending))
for seq, p := range state.Pending {
pending[seq] = &Pending{p.Sequence, p.Timestamp}
if seq <= state.AckFloor.Stream || seq > state.Delivered.Stream {
return fmt.Errorf("bad pending entry, sequence [%d] out of range", seq)
}
}
}
if len(state.Redelivered) > 0 {
redelivered = make(map[uint64]uint64, len(state.Redelivered))
for seq, dc := range state.Redelivered {
redelivered[seq] = dcView on GitHub (pinned to 3a66a489d2)
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)
Example fix
// before
if err := store.Update(&badState); err != nil { ... } // bad ack floor for stream
// after
if badState.AckFloor.Stream > badState.Delivered.Stream {
badState.AckFloor = badState.Delivered
}
if err := store.Update(&badState); err != nil { ... } Defensive patterns
Strategy: validation
Validate before calling
func validAckFloor(s *ConsumerState) bool {
return s.AckFloor.Stream <= s.Delivered.Stream && s.AckFloor.Consumer <= s.Delivered.Consumer
}
if !validAckFloor(st) { return fmt.Errorf("refusing Update: ack floor ahead of delivered") } Type guard
func stateIsConsistent(s *ConsumerState) bool {
return s != nil && s.AckFloor.Stream <= s.Delivered.Stream
} Try / catch
if err := store.Update(st); err != nil {
if strings.Contains(err.Error(), "bad ack floor") {
// rebuild state from stream instead of retrying
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- bad pending entry, sequence [%d] out of range
- incomplete type, value pair
- DN ended with incomplete type, value pair
- errors.New(strings.Join(errs, "\n"))
- bad consumer config
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/dc1256ecf7a65b30.
Report an issue: GitHub.