{"record":{"id":"a3b48042f239ad14","repo":"nats-io/nats-server","slug":"expected-state-json-first","errorCode":null,"errorMessage":"expected state.json first","messagePattern":"expected state\\.json first","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/stream_backup.go","lineNumber":260,"sourceCode":"\tif err = writeGeneric(_EMPTY_, 0, 0, 0, 0, nil); err != nil {\n\t\terrCh <- err\n\t}\n}\n\n// RestoreStreamSnapshotV2 will restore a stream from a snapshot.\nfunc (a *Account) RestoreStreamV2(ncfg *StreamConfig, r io.Reader) (retMset *stream, retErr error) {\n\tdec := s2.NewReader(r)\n\ttr := archive.NewReader(dec)\n\n\tvar nstate StreamState\n\n\t// Load the stream state.\n\thdr, err := tr.Next()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif hdr.Name != \"state.json\" {\n\t\treturn nil, fmt.Errorf(\"expected state.json first\")\n\t}\n\tstate, err := io.ReadAll(tr)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"expected state.json contents\")\n\t}\n\tif err := json.Unmarshal(state, &nstate); err != nil {\n\t\treturn nil, fmt.Errorf(\"error in state.json: %w\", err)\n\t}\n\n\ts, jsa, err := a.checkForJetStream()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tjs := jsa.js\n\tif js == nil {\n\t\treturn nil, NewJSNotEnabledForAccountError()\n\t}\n\tif _, err := a.lookupStream(ncfg.Name); err == nil {","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/stream_backup.go#L242-L278","documentation":"RestoreStreamV2 restores a JetStream stream from an s2-compressed tar-like archive. The very first archive entry must be 'state.json' holding the stream's StreamState snapshot; if the first entry's header name differs, the server aborts the restore because it cannot know the stream's sequence/consumer layout before writing any messages. This guards against restoring a corrupted, truncated, or non-snapshot archive.","triggerScenarios":"Calling Account.RestoreStreamV2 (or the JS API $JS.API.STREAM.RESTORE subject, handled by processStreamRestore) with a reader whose decompressed archive's first tar entry is not named 'state.json' — e.g. an archive created by a different tool, hand-built tar, a v1-format snapshot, or an archive reordered/truncated so the header read lands on the wrong entry.","commonSituations":"Re-wrapping or post-processing a backup file with tar/zip tools that renamed or reordered entries; restoring a file produced by another JetStream version or by a custom exporter; feeding an empty/corrupt or partially-downloaded snapshot file; pointing the restore at a plain (non-s2, non-archive) file.","solutions":["Re-take the stream backup with the nats-server snapshot path (nats stream backup / StreamSnapshot) so the archive starts with state.json","Verify the archive's first entry: open with s2 reader + tar reader and check hdr.Name == \"state.json\" before calling the API","If the file was truncated during transfer, re-download/re-copy it and compare checksums with the source","If using a custom writer, ensure writeGeneric's first call writes the 'state.json' entry before consumers/messages, matching the snapshot layout"],"exampleFix":"// before: restoring an arbitrary tar file\nf, _ := os.Open(\"stream.tar\")\nmset, err := acc.RestoreStreamV2(cfg, f)\n// after: verify it is an s2 snapshot archive whose first entry is state.json\nf, _ := os.Open(\"stream.tar.bak\")\nzr := s2.NewReader(f)\ntr := archive.NewReader(zr)\nhdr, err := tr.Next()\nif err != nil || hdr.Name != \"state.json\" {\n    return fmt.Errorf(\"not a valid stream snapshot archive\")\n}\nf.Seek(0, 0)\nmset, err := acc.RestoreStreamV2(cfg, f)","handlingStrategy":"validation","validationCode":"func isValidSnapshot(r io.ReadSeeker) bool {\n    zr := s2.NewReader(r)\n    tr := archive.NewReader(zr)\n    hdr, err := tr.Next()\n    ok := err == nil && hdr != nil && hdr.Name == \"state.json\"\n    r.Seek(0, 0)\n    return ok\n}","typeGuard":null,"tryCatchPattern":"mset, err := acc.RestoreStreamV2(cfg, r)\nif err != nil {\n    if strings.Contains(err.Error(), \"expected state.json first\") {\n        // treat as invalid archive: re-snapshot the source stream\n    }\n    return err\n}","preventionTips":["Only restore archives produced by nats-server StreamSnapshot / `nats stream backup`","Never reorder or rename entries when repacking snapshot archives","Verify the first tar entry is state.json before calling the restore API","Keep backup and restore nats-server versions compatible"],"tags":["jetstream","stream-restore","archive-format"],"backgroundTag":"invalid-snapshot-archive","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}