nats-io/nats-server · error
bad ack floor for consumer
Error message
bad ack floor for consumer
What it means
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.
Source
Thrown at server/filestore.go:13871
clone.Name = o.name
return clone
}
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 {View on GitHub (pinned to 3a66a489d2)
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.
Example fix
// before state.AckFloor.Consumer = 150; state.Delivered.Consumer = 120 // invalid // after state.AckFloor.Consumer = 120; state.Delivered.Consumer = 150
Defensive patterns
Strategy: validation
Validate before calling
func stateSane(st *ConsumerState) bool {
return st.AckFloor.Consumer <= st.Delivered.Consumer &&
st.AckFloor.Stream <= st.Delivered.Stream
} Type guard
func ackFloorOK(st *ConsumerState) bool {
return st != nil && st.AckFloor.Consumer <= st.Delivered.Consumer
} Try / catch
if !stateSane(state) {
return errors.New("refusing update: ack floor exceeds delivered")
}
// then call Update and handle the error Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- bad consumer config
- could not create consumer directory - %v
- bad ack floor for stream
- bad pending entry, sequence [%d] out of range
- uncompressed buffer is too short
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/246752ab4753cddd.
Report an issue: GitHub.