{"record":{"id":"e005c89cdf5a9d9b","repo":"dgraph-io/dgraph","slug":"unexpected-empty-request","errorCode":null,"errorMessage":"unexpected empty request","messagePattern":"unexpected empty request","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"worker/import.go","lineNumber":410,"sourceCode":"\nfunc pipeTwoStream(in api.Dgraph_StreamExtSnapshotServer, out pb.Worker_StreamExtSnapshotClient, groupId uint32) error {\n\tcurrentGroup := groups().Node.gid\n\tctx := in.Context()\n\n\tfor {\n\t\tif err := ctx.Err(); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treq, err := in.Recv()\n\t\tif errors.Is(err, io.EOF) {\n\t\t\treturn nil\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"recv upstream(%d): %w\", currentGroup, err)\n\t\t}\n\t\tif req.Pkt == nil {\n\t\t\treturn fmt.Errorf(\"unexpected empty request\")\n\t\t}\n\n\t\tif req.Pkt.Done {\n\t\t\t// Forward Done, half-close downstream send.\n\t\t\tif err := out.Send(&api.StreamExtSnapshotRequest{Pkt: req.Pkt}); err != nil && !errors.Is(err, io.EOF) {\n\t\t\t\treturn fmt.Errorf(\"send done downstream(%d): %w\", groupId, err)\n\t\t\t}\n\t\t\t_ = out.CloseSend()\n\n\t\t\t// Drain downstream and relay upstream until Finish=true.\n\t\t\tfor {\n\t\t\t\tif err := ctx.Err(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tresp, err := out.Recv()\n\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\treturn fmt.Errorf(\"downstream(%d) closed before Finish=true\", groupId)\n\t\t\t\t}","sourceCodeStart":392,"sourceCodeEnd":428,"githubUrl":"https://github.com/dgraph-io/dgraph/blob/759e242be62c91f8d084da06ad0c8d21256d9c07/worker/import.go#L392-L428","documentation":"While relaying, pipeTwoStream expects every upstream message to carry a Pkt payload. If req.Pkt is nil the protocol contract is broken — the peer sent a control-less or malformed frame — and piping is aborted with this error rather than forwarding a nil packet downstream.","triggerScenarios":"A peer sends api.StreamExtSnapshotRequest with neither Forward nor Pkt set (e.g. wrong client version, hand-rolled test client, or corrupted message after a deserialization edge case).","commonSituations":"Mixed Dgraph versions where the wire schema differs; a custom tool or script speaking the streaming protocol incorrectly; a proxy that truncates/reorders frames.","solutions":["Ensure the client and all alphas run compatible Dgraph versions","Find which peer sent the malformed frame via the surrounding [import] logs and fix or upgrade it","If using a custom importer, always populate Pkt (with Done and/or data KVs) on every non-Forward message"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// custom clients must validate every outgoing message before Send\nfunc validMsg(m *api.StreamExtSnapshotRequest) bool {\n    return m.Forward || m.Pkt != nil\n}","typeGuard":"func hasPkt(req *api.StreamExtSnapshotRequest) bool {\n    return req != nil && req.Pkt != nil\n}","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"unexpected empty request\") {\n    // protocol violation by a peer; identify peer version and upgrade\n}","preventionTips":["Never mix Dgraph versions in a cluster","Always populate Pkt on non-Forward stream messages","Test custom importers against the real gRPC schema","Log and drop malformed frames at the client before sending"],"tags":["protocol","grpc","validation","streaming"],"backgroundTag":"malformed-stream-message","analyzedSha":"759e242be62c91f8d084da06ad0c8d21256d9c07","analyzedAt":"2026-09-01T14:42:12.034Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}