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

  1. Fix the ConsumerState so AckFloor.Consumer <= Delivered.Consumer before calling Update.
  2. If from a state file, restore the consumer's state file from backup or delete the consumer and recreate it.
  3. Ensure any custom ack-tracking code advances Delivered before/with AckFloor.
  4. 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

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


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/246752ab4753cddd. Report an issue: GitHub.