{"record":{"id":"4e5c1588c3c0bd97","repo":"thanos-io/thanos","slug":"unmarshal-storepb-seriesresponse-frame-for-file-s","errorCode":null,"errorMessage":"unmarshal storepb.SeriesResponse frame for file %s","messagePattern":"unmarshal storepb\\.SeriesResponse frame for file (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/store/local.go","lineNumber":86,"sourceCode":"\t\tc:         f,\n\t}\n\n\t// Do quick pass for in-mem index.\n\tcontent := f.Bytes()\n\tcontentStart := bytes.Index(content, []byte(\"{\"))\n\tif contentStart != -1 {\n\t\tcontent = content[contentStart:]\n\t}\n\n\tif idx := bytes.LastIndex(content, []byte(\"}\")); idx != -1 {\n\t\tcontent = content[:idx+1]\n\t}\n\n\tskanner := NewNoCopyScanner(content, split)\n\tresp := &storepb.SeriesResponse{}\n\tfor skanner.Scan() {\n\t\tif err := jsonpb.Unmarshal(bytes.NewReader(skanner.Bytes()), resp); err != nil {\n\t\t\treturn nil, errors.Wrapf(err, \"unmarshal storepb.SeriesResponse frame for file %s\", path)\n\t\t}\n\t\tseries := resp.GetSeries()\n\t\tif series == nil {\n\t\t\tlevel.Warn(logger).Log(\"msg\", \"not a valid series\", \"frame\", resp.String())\n\t\t\tcontinue\n\t\t}\n\t\tchks := make([]int, 0, len(series.Chunks))\n\t\t// Sort chunks in separate slice by MinTime for easier lookup. Find global max and min.\n\t\tfor ci := range series.Chunks {\n\t\t\tchks = append(chks, ci)\n\t\t}\n\n\t\tsort.Slice(chks, func(i, j int) bool {\n\t\t\treturn series.Chunks[chks[i]].MinTime < series.Chunks[chks[j]].MinTime\n\t\t})\n\t\ts.series = append(s.series, series)\n\t\ts.sortedChunks = append(s.sortedChunks, chks)\n\t}","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/pkg/store/local.go#L68-L104","documentation":"NewLocalStoreFromJSONMmappableFile reads a file of newline-delimited storepb.SeriesResponse frames (grpcurl output) and unmarshals each frame with jsonpb; any decode failure is wrapped with the file path. It means one of the JSON frames does not match the storepb.SeriesResponse schema.","triggerScenarios":"Loading a cached/dumped store response file where a frame was truncated, written by an incompatible proto version, or is not a SeriesResponse JSON at all.","commonSituations":"Manually captured grpcurl output edited/corrupted; version mismatch between the tool that dumped the file and Thanos proto definitions; partially written file from an interrupted dump.","solutions":["Regenerate the dump with the same Thanos proto version used to read it.","Validate the offending line is valid JSON matching storepb.SeriesResponse (jq, protojson).","Remove or fix the corrupted frame in the file.","Ensure the file is a complete, non-truncated dump."],"exampleFix":"// before\ngrpcurl -plaintext store:10901 thanos.StoreInfo > dump.json  # mixed responses\n// after\ngrpcurl -plaintext -emit-defaults store:10901 thanos.Store/Series > series_frames.ndjson","handlingStrategy":"validation","validationCode":"// validate each line before loading\nfor i, line := range lines {\n  var probe map[string]json.RawMessage\n  if json.Unmarshal([]byte(line), &probe) != nil || probe[\"series\"] == nil {\n    return fmt.Errorf(\"frame %d not a SeriesResponse\", i)\n  }\n}","typeGuard":null,"tryCatchPattern":"if err != nil {\n  var perr *json.UnmarshalTypeError\n  if errors.As(err, &perr) { /* report offending line/field */ }\n}","preventionTips":["Regenerate dumps whenever Thanos proto version changes","Never hand-edit dumped frame files","Verify dumps are fully flushed before use"],"tags":["go","json","protobuf"],"backgroundTag":"protobuf-unmarshal-failed","analyzedSha":"35b8b991177def87ed52dcf10f9b6d87f07282c8","analyzedAt":"2026-09-07T01:49:59.689Z","contentChangedAt":"2026-09-07T01:49:59.689Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}