nats-io/nats-server · error

expected consumer, found %q

Error message

expected consumer, found %q

What it means

The snapshot archive must list consumer entries as 'consumers/<name>' in the order declared by nstate.Consumers. While reading the expected consumer entries, if an archive entry's name lacks the 'consumers/' prefix, the restore aborts — the archive layout does not match the snapshot contract. Note: this loop also runs when nstate.Consumers == 0, in which case tr.Next() may return an end sentinel handled separately.

Source

Thrown at server/stream_backup.go:418

			o.switchToEphemeral()
		}
		for _, o := range restoredConsumers {
			if err := o.completeRestore(); err != nil {
				if err = fmt.Errorf("failed to activate consumer %q: %w", o.name, err); retErr == nil {
					retErr = err
				}
				s.Warnf("JetStream stream restore for '%s > %s' failed to activate consumers: %v", a.Name, cfg.Name, err)
			}
		}
	}()
	for range nstate.Consumers {
		hdr, err := tr.Next()
		if err != nil {
			return nil, err
		}
		name, found := strings.CutPrefix(hdr.Name, "consumers/")
		if !found {
			return nil, fmt.Errorf("expected consumer, found %q", hdr.Name)
		}
		buf, err := io.ReadAll(tr)
		if err != nil {
			return nil, fmt.Errorf("failed to read consumer %q state: %w", name, err)
		}
		var consumer SnapshotConsumerState
		if err := json.Unmarshal(buf, &consumer); err != nil {
			return nil, fmt.Errorf("failed to decode consumer %q state: %w", name, err)
		}
		if consumer.ConsumerConfig == nil {
			return nil, fmt.Errorf("consumer %q is missing config", name)
		}
		if consumer.ConsumerState == nil {
			return nil, fmt.Errorf("consumer %q is missing state", name)
		}
		isEphemeral := !isDurableConsumer(consumer.ConsumerConfig)
		if isEphemeral {
			// Keep ephemerals alive and interested until all messages have

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Re-create the snapshot with nats stream backup / StreamSnapshot so entry names follow the consumers/<name> convention
  2. Inspect the archive entries (decompress with s2, list tar headers) and confirm each consumer entry starts with 'consumers/'
  3. Ensure state.json's Consumers count matches the number of consumers/<name> entries in the archive
  4. If repacking manually, keep exact entry names and ordering: state.json, then each consumers/<name> entry, then messages

Example fix

// before: repacked archive entry named 'consumer_orders'
// after: correct snapshot layout entry name
"consumers/orders"
Defensive patterns

Strategy: validation

Validate before calling

func checkConsumerEntries(r io.ReadSeeker, want int) error {
    tr := archive.NewReader(s2.NewReader(r))
    if h, err := tr.Next(); err != nil || h.Name != "state.json" {
        return fmt.Errorf("not a snapshot archive")
    }
    io.Copy(io.Discard, tr)
    for i := 0; i < want; i++ {
        h, err := tr.Next()
        if err != nil || !strings.HasPrefix(h.Name, "consumers/") {
            return fmt.Errorf("entry %d not a consumer entry", i)
        }
        io.Copy(io.Discard, tr)
    }
    return nil
}

Try / catch

_, err := acc.RestoreStreamV2(cfg, r)
if err != nil {
    if strings.Contains(err.Error(), "expected consumer, found") {
        // archive layout mismatch: regenerate snapshot with nats stream backup
    }
    return err
}

Prevention

When it happens

Trigger: A snapshot archive whose entries were renamed/reordered by external tooling (tar re-pack dropping the consumers/ prefix), a v1-format or custom backup lacking the consumers/ naming, or state.json declaring more consumers than the archive actually contains so the reader lands on a non-consumer entry (e.g. a message chunk or sentinel).

Common situations: Hand-repacking a backup with tar and losing the expected entry names; mixing backup formats between nats-server versions; a truncated archive where the message section bleeds into where consumer entries were expected; custom export pipelines generating wrong entry names.

Related errors


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