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
- Prune Pending entries with seq <= AckFloor.Stream before calling Update
- Drop Pending entries with seq > Delivered.Stream; they must be redelivered via the stream, not stored as pending
- Rebuild pending from the stream's delivered-but-unacked messages instead of importing foreign state
- 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
- Prune pending entries whenever the ack floor advances
- Never merge Pending maps from different consumers or streams
- Snapshot ack floor, delivered, and pending atomically
- Validate pending window before any Update/ForceUpdate
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
- errors.New(strings.Join(errs, "\n"))
- bad ack floor for stream
- incomplete type, value pair
- DN ended with incomplete type, value pair
- bad consumer config
AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02).
Data as JSON: /api/errors/9b0c6a039294772c.
Report an issue: GitHub.