{"record":{"id":"bc71fbac6693f28f","repo":"Tencent/WeKnora","slug":"grpc-read-failed-w","errorCode":null,"errorMessage":"gRPC Read failed: %w","messagePattern":"gRPC Read failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/infrastructure/docparser/grpc_parser.go","lineNumber":207,"sourceCode":"\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(),\n\t\tError:           resp.GetError(),\n\t}\n\tif refs := resp.GetImageRefs(); len(refs) > 0 {\n\t\tresult.ImageRefs = make([]types.ImageRef, 0, len(refs))\n\t\tfor _, img := range refs {\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})","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/infrastructure/docparser/grpc_parser.go#L189-L225","documentation":"readUnary wraps any error from the legacy unary Read RPC with 'gRPC Read failed: %w'. This path is only reached as a compatibility fallback when the connected docreader reports codes.Unimplemented for ReadStream, so the wrapped error is always an underlying unary RPC failure (transport error, gRPC status like Unavailable/DeadlineExceeded/ResourceExhausted, or an older server rejecting the request).","triggerScenarios":"Read is called, ReadStream returns codes.Unimplemented (docreader build predates streaming), the client falls back to readUnary, and then client.Read fails — e.g. server down, context deadline exceeded, message larger than the unary max size (MAX_FILE_SIZE_MB / 50MB default), or auth/TLS rejection.","commonSituations":"Large scanned PDFs exceeding the unary message-size limit (the very limit streaming was introduced to avoid); docreader pod restarting or unreachable; version-skewed deployment forcing the legacy path; expired auth token or misconfigured TLS on the gRPC channel; per-request context timeout too short for big documents.","solutions":["Unwrap the error with errors.Unwrap or status.Convert(err) to see the real gRPC code and fix that root cause first","Check docreader availability (kubectl get pods / service endpoint) and network reachability from the client","If the error is ResourceExhausted, raise MAX_FILE_SIZE_MB on both sides or upgrade the docreader so streaming (ReadStream) is used instead of the unary fallback","Upgrade the docreader build to one that implements ReadStream so the unary fallback path is not used at all","Verify TLS/auth env config used by docclient.LoadAuthConfigFromEnv matches the server's expectations"],"exampleFix":"// before\nresult, err := reader.Read(ctx, req)\nif err != nil {\n    return fmt.Errorf(\"parse failed: %w\", err)\n}\n// after\nresult, err := reader.Read(ctx, req)\nif err != nil {\n    if st, ok := status.FromError(errors.Unwrap(err)); ok && st.Code() == codes.ResourceExhausted {\n        return fmt.Errorf(\"document too large for legacy unary path (max %d MB); upgrade docreader to enable streaming\", maxSizeMB)\n    }\n    return fmt.Errorf(\"parse failed: %w\", err)\n}","handlingStrategy":"try-catch","validationCode":"if !reader.IsConnected() {\n    return fmt.Errorf(\"docreader gRPC client not connected\")\n}\n// Keep documents under the unary limit since the fallback path is size-capped:\nconst maxUnaryBytes = 50 * 1024 * 1024 // or read MAX_FILE_SIZE_MB\nif len(req.FileContent) > maxUnaryBytes {\n    return fmt.Errorf(\"document %s exceeds unary fallback limit (%d bytes); streaming docreader required\", req.FileName, maxUnaryBytes)\n}","typeGuard":null,"tryCatchPattern":"result, err := reader.Read(ctx, req)\nif err != nil {\n    var inner error = err\n    for errors.Unwrap(inner) != nil { inner = errors.Unwrap(inner) }\n    if st, ok := status.FromError(inner); ok {\n        switch st.Code() {\n        case codes.ResourceExhausted:\n            return nil, fmt.Errorf(\"document too large for legacy unary path: %w\", err)\n        case codes.Unavailable:\n            return nil, fmt.Errorf(\"docreader unavailable: %w\", err) // retry with backoff\n        default:\n            return nil, fmt.Errorf(\"docreader read failed (%s): %w\", st.Code(), err)\n        }\n    }\n    return nil, err\n}","preventionTips":["Deploy a docreader build that implements ReadStream so the size-capped unary fallback is never used","Set MAX_FILE_SIZE_MB consistently on client and server","Use per-request contexts with deadlines sized for large documents","Monitor gRPC status codes (Unavailable, ResourceExhausted) and alert on them"],"tags":["grpc","rpc-failure","fallback","version-skew"],"backgroundTag":"grpc-unary-call-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}