{"record":{"id":"e958d70ec246ee58","repo":"Tencent/WeKnora","slug":"grpc-readstream-returned-no-metadata-frame","errorCode":null,"errorMessage":"gRPC ReadStream returned no metadata frame","messagePattern":"gRPC ReadStream returned no metadata frame","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/infrastructure/docparser/grpc_parser.go","lineNumber":195,"sourceCode":"\t\t\tif n := meta.GetImageCount(); n > 0 {\n\t\t\t\tresult.ImageRefs = make([]types.ImageRef, 0, n)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif img := frame.GetImage(); img != nil {\n\t\t\tresult.ImageRefs = append(result.ImageRefs, types.ImageRef{\n\t\t\t\tFilename:    img.GetFilename(),\n\t\t\t\tOriginalRef: img.GetOriginalRef(),\n\t\t\t\tMimeType:    img.GetMimeType(),\n\t\t\t\tStorageKey:  img.GetStorageKey(),\n\t\t\t\tImageData:   img.GetImageData(),\n\t\t\t})\n\t\t}\n\t}\n\n\tif !gotMeta {\n\t\treturn nil, fmt.Errorf(\"gRPC ReadStream returned no metadata frame\")\n\t}\n\treturn result, nil\n}\n\n// readUnary calls the legacy unary Read RPC. Used only as a compatibility\n// fallback when the connected docreader does not implement ReadStream.\nfunc (p *GRPCDocumentReader) readUnary(\n\tctx context.Context, client proto.DocReaderClient, protoReq *proto.ReadRequest,\n) (*types.ReadResult, error) {\n\tresp, err := client.Read(ctx, protoReq)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"gRPC Read failed: %w\", err)\n\t}\n\n\tresult := &types.ReadResult{\n\t\tMarkdownContent: resp.GetMarkdownContent(),\n\t\tImageDirPath:    resp.GetImageDirPath(),\n\t\tMetadata:        resp.GetMetadata(),","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/infrastructure/docparser/grpc_parser.go#L177-L213","documentation":"This error is returned by GRPCDocumentReader.readStream after consuming the server-streaming ReadStream RPC to completion (io.EOF) without ever receiving a frame whose meta field is populated. The protocol contract is one metadata frame first, followed by one frame per image; if the stream ends with gotMeta still false, the client cannot build a valid ReadResult and fails closed rather than returning an empty result.","triggerScenarios":"Calling Read on a GRPCDocumentReader where the docreader server closes the ReadStream stream (sends only io.EOF) without ever sending a meta frame — e.g. a server that sends only image frames, a silently dropped/empty stream, or a server-side bug that skips the meta frame on certain documents.","commonSituations":"Version-skewed deployments where the docreader side implements ReadStream but with different framing semantics (e.g. older/patched builds that omit the meta frame when there is no content); a proxy or load balancer terminating the stream after connect; a server crash mid-handshake after opening the stream but before sending the first frame.","solutions":["Check the docreader server version and logs — upgrade/align the docreader deployment with the client so the ReadStream implementation sends the meta frame first","Inspect the specific document: try parsing a small, well-formed file to see if the missing meta frame is document-specific (server-side parse failure returning an empty stream)","Confirm no intermediate proxy/LB is cutting the stream short — test the docreader service directly with grpcurl or a minimal client","Check result.Error handling on the server: ensure server-side parse errors are conveyed via the meta frame's error field rather than closing the stream silently","As a last resort, retry the Read; if it reproduces consistently, capture a gRPC trace of the stream frames"],"exampleFix":"// before\nresult, err := reader.Read(ctx, req)\nif err != nil {\n    return err // opaque 'no metadata frame' failure\n}\n// after\nresult, err := reader.Read(ctx, req)\nif err != nil {\n    if strings.Contains(err.Error(), \"returned no metadata frame\") {\n        logger.Warnf(ctx, \"docreader sent empty/malformed stream for %s, check server version\", req.FileName)\n    }\n    return err\n}","handlingStrategy":"validation","validationCode":"if !reader.IsConnected() {\n    return fmt.Errorf(\"docreader not connected; cannot read %s\", req.FileName)\n}\n// Optionally probe server capability once at startup so version-skew is\ndetected before document reads:\n_, err := reader.ListEngines(ctx, nil)\nif err != nil {\n    return fmt.Errorf(\"docreader capability probe failed: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"result, err := reader.Read(ctx, req)\nif err != nil {\n    if strings.Contains(err.Error(), \"returned no metadata frame\") {\n        // malformed/empty stream: retry once, then surface a document-specific error\n        if result, rerr := reader.Read(ctx, req); rerr == nil {\n            return result, nil\n        }\n        return nil, fmt.Errorf(\"docreader returned malformed stream for %s (check server version)\", req.FileName)\n    }\n    return nil, err\n}","preventionTips":["Keep docreader server and client versions aligned so ReadStream framing always includes the meta frame first","Check result.Error from the meta frame path rather than letting the server close streams silently on parse failure","Avoid proxies/LBs that can truncate long-lived gRPC streams; configure generous idle timeouts","Test with a small known-good document after every docreader upgrade"],"tags":["grpc","streaming","protocol-violation","docparser"],"backgroundTag":"grpc-empty-stream-response","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}