nats-io/nats-server · error

bad pending entry, sequence [%d] out of range

Error message

bad pending entry, sequence [%d] out of range

What it means

consumerFileStore.Update() validates each pending sequence as it copies state.Pending: every pending sequence must be strictly greater than AckFloor.Stream (not yet acked) and not greater than Delivered.Stream (already delivered). A sequence outside (AckFloor.Stream, Delivered.Stream] is impossible state. Thrown from server/filestore.go.

Source

Thrown at server/filestore.go:13885

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] = dc
		}
	}

	// Replace our state.
	o.mu.Lock()
	defer o.mu.Unlock()

	// Check to see if this is an outdated update.
	if state.Delivered.Consumer < o.state.Delivered.Consumer || state.AckFloor.Stream < o.state.AckFloor.Stream {
		return ErrStoreOldUpdate
	}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Prune Pending entries with seq <= AckFloor.Stream before calling Update
  2. Drop Pending entries with seq > Delivered.Stream; they must be redelivered via the stream, not stored as pending
  3. Rebuild pending from the stream's delivered-but-unacked messages instead of importing foreign state
  4. Log the offending seq plus ack floor/delivered to identify which snapshot source is wrong

Example fix

// before
for seq, p := range state.Pending {
	if seq <= state.AckFloor.Stream { /* stale, will fail Update */ }
}
store.Update(state) // bad pending entry, sequence [%d] out of range
// after
for seq := range state.Pending {
	if seq <= state.AckFloor.Stream || seq > state.Delivered.Stream {
		delete(state.Pending, seq)
	}
}
store.Update(state)
Defensive patterns

Strategy: validation

Validate before calling

func prunePending(st *ConsumerState) {
	for seq := range st.Pending {
		if seq <= st.AckFloor.Stream || seq > st.Delivered.Stream {
			delete(st.Pending, seq)
		}
	}
}
prunePending(st); store.Update(st)

Type guard

func pendingInRange(st *ConsumerState) bool {
	for seq := range st.Pending {
		if seq <= st.AckFloor.Stream || seq > st.Delivered.Stream { return false }
	}
	return true
}

Try / catch

if err := store.Update(st); err != nil {
	if strings.Contains(err.Error(), "bad pending entry") {
		// log offending seq from err text, prune, retry once
	}
	return err
}

Prevention

When it happens

Trigger: Passing a ConsumerState whose Pending map contains a sequence <= AckFloor.Stream (already acked) or > Delivered.Stream (never delivered) to Update().

Common situations: Merging pending maps from two snapshots; a consumer whose ack floor advanced but stale pending entries weren't pruned; importing state from a different stream (different sequence ranges); restoring state captured before/after compaction mismatch.

Related errors


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