{"record":{"id":"03d6bbe2b5c7ba97","repo":"thanos-io/thanos","slug":"empty-exemplars-data","errorCode":null,"errorMessage":"empty exemplars data","messagePattern":"empty exemplars data","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/exemplars/exemplars.go","lineNumber":54,"sourceCode":"\t// This field just exist to pseudo-implement the unused methods of the interface.\n\texemplarspb.Exemplars_ExemplarsServer\n\tctx context.Context\n\n\twarnings annotations.Annotations\n\tdata     []*exemplarspb.ExemplarData\n\tmu       sync.Mutex\n}\n\nfunc (srv *exemplarsServer) Send(res *exemplarspb.ExemplarsResponse) error {\n\tif res.GetWarning() != \"\" {\n\t\tsrv.mu.Lock()\n\t\tdefer srv.mu.Unlock()\n\t\tsrv.warnings.Add(errors.New(res.GetWarning()))\n\t\treturn nil\n\t}\n\n\tif res.GetData() == nil {\n\t\treturn errors.New(\"empty exemplars data\")\n\t}\n\n\tsrv.mu.Lock()\n\tdefer srv.mu.Unlock()\n\tsrv.data = append(srv.data, res.GetData())\n\treturn nil\n}\n\nfunc (srv *exemplarsServer) Context() context.Context {\n\treturn srv.ctx\n}\n\nfunc NewGRPCClient(es exemplarspb.ExemplarsServer) *GRPCClient {\n\treturn NewGRPCClientWithDedup(es, nil)\n}\n\nfunc NewGRPCClientWithDedup(es exemplarspb.ExemplarsServer, replicaLabels []string) *GRPCClient {\n\tc := &GRPCClient{","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/pkg/exemplars/exemplars.go#L36-L72","documentation":"exemplarsServer.Send returns this error when an ExemplarsResponse arrives with no warning but a nil Data payload. Such a response carries nothing usable, so the stream treats it as a protocol violation rather than silently appending nil data.","triggerScenarios":"A downstream server sends exemplarspb.ExemplarsResponse{} or &ExemplarsResponse{Result: nil} over the stream, causing srv.Send to hit `res.GetData() == nil` and return errors.New(\"empty exemplars data\").","commonSituations":"Buggy or version-mismatched store implementations that send empty responses, middleware altering responses and dropping the Result field, or tests constructing responses by hand and forgetting to call NewExemplarsResponse.","solutions":["Fix the sender to always wrap data with exemplarspb.NewExemplarsResponse(data) before Send.","If a store genuinely has no data, it should send no response rather than an empty one.","Check for version mismatches between client and store (older ThanOS stores with different response shapes).","In tests, construct responses via the NewExemplarsResponse helper instead of raw struct literals."],"exampleFix":"// before (sender side)\nstream.Send(&exemplarspb.ExemplarsResponse{})\n// after\nstream.Send(exemplarspb.NewExemplarsResponse(data))","handlingStrategy":"validation","validationCode":"func validResponse(res *exemplarspb.ExemplarsResponse) bool {\n\treturn res.GetWarning() != \"\" || res.GetData() != nil\n}","typeGuard":"func hasExemplarData(res *exemplarspb.ExemplarsResponse) bool {\n\treturn res != nil && res.GetData() != nil\n}","tryCatchPattern":null,"preventionTips":["Only send responses via exemplarspb.NewExemplarsResponse(data).","Send nothing when there is no data rather than an empty response.","Cover senders with tests asserting non-nil Data."],"tags":["go","grpc","streaming","exemplars","protobuf"],"backgroundTag":"unexpected-response-shape","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"}