{"record":{"id":"7864b94d0c836ac6","repo":"nats-io/nats-server","slug":"expected-consumer-found-q","errorCode":null,"errorMessage":"expected consumer, found %q","messagePattern":"expected consumer, found %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/stream_backup.go","lineNumber":418,"sourceCode":"\t\t\to.switchToEphemeral()\n\t\t}\n\t\tfor _, o := range restoredConsumers {\n\t\t\tif err := o.completeRestore(); err != nil {\n\t\t\t\tif err = fmt.Errorf(\"failed to activate consumer %q: %w\", o.name, err); retErr == nil {\n\t\t\t\t\tretErr = err\n\t\t\t\t}\n\t\t\t\ts.Warnf(\"JetStream stream restore for '%s > %s' failed to activate consumers: %v\", a.Name, cfg.Name, err)\n\t\t\t}\n\t\t}\n\t}()\n\tfor range nstate.Consumers {\n\t\thdr, err := tr.Next()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tname, found := strings.CutPrefix(hdr.Name, \"consumers/\")\n\t\tif !found {\n\t\t\treturn nil, fmt.Errorf(\"expected consumer, found %q\", hdr.Name)\n\t\t}\n\t\tbuf, err := io.ReadAll(tr)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read consumer %q state: %w\", name, err)\n\t\t}\n\t\tvar consumer SnapshotConsumerState\n\t\tif err := json.Unmarshal(buf, &consumer); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to decode consumer %q state: %w\", name, err)\n\t\t}\n\t\tif consumer.ConsumerConfig == nil {\n\t\t\treturn nil, fmt.Errorf(\"consumer %q is missing config\", name)\n\t\t}\n\t\tif consumer.ConsumerState == nil {\n\t\t\treturn nil, fmt.Errorf(\"consumer %q is missing state\", name)\n\t\t}\n\t\tisEphemeral := !isDurableConsumer(consumer.ConsumerConfig)\n\t\tif isEphemeral {\n\t\t\t// Keep ephemerals alive and interested until all messages have","sourceCodeStart":400,"sourceCodeEnd":436,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/stream_backup.go#L400-L436","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Re-create the snapshot with nats stream backup / StreamSnapshot so entry names follow the consumers/<name> convention","Inspect the archive entries (decompress with s2, list tar headers) and confirm each consumer entry starts with 'consumers/'","Ensure state.json's Consumers count matches the number of consumers/<name> entries in the archive","If repacking manually, keep exact entry names and ordering: state.json, then each consumers/<name> entry, then messages"],"exampleFix":"// before: repacked archive entry named 'consumer_orders'\n// after: correct snapshot layout entry name\n\"consumers/orders\"","handlingStrategy":"validation","validationCode":"func checkConsumerEntries(r io.ReadSeeker, want int) error {\n    tr := archive.NewReader(s2.NewReader(r))\n    if h, err := tr.Next(); err != nil || h.Name != \"state.json\" {\n        return fmt.Errorf(\"not a snapshot archive\")\n    }\n    io.Copy(io.Discard, tr)\n    for i := 0; i < want; i++ {\n        h, err := tr.Next()\n        if err != nil || !strings.HasPrefix(h.Name, \"consumers/\") {\n            return fmt.Errorf(\"entry %d not a consumer entry\", i)\n        }\n        io.Copy(io.Discard, tr)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"_, err := acc.RestoreStreamV2(cfg, r)\nif err != nil {\n    if strings.Contains(err.Error(), \"expected consumer, found\") {\n        // archive layout mismatch: regenerate snapshot with nats stream backup\n    }\n    return err\n}","preventionTips":["Use nats stream backup / StreamSnapshot to build archives; don't repack by hand","Keep the consumers/<name> entry naming and count matching state.json's Consumers","Never mix snapshot formats across backup tool versions","Validate archive entry names before restoring"],"tags":["jetstream","stream-restore","archive-format","consumer"],"backgroundTag":"invalid-snapshot-archive","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}