Tencent/WeKnora · error

gRPC Read failed: %w

Error message

gRPC Read failed: %w

What it means

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).

Source

Thrown at internal/infrastructure/docparser/grpc_parser.go:207

				ImageData:   img.GetImageData(),
			})
		}
	}

	if !gotMeta {
		return nil, fmt.Errorf("gRPC ReadStream returned no metadata frame")
	}
	return result, nil
}

// readUnary calls the legacy unary Read RPC. Used only as a compatibility
// fallback when the connected docreader does not implement ReadStream.
func (p *GRPCDocumentReader) readUnary(
	ctx context.Context, client proto.DocReaderClient, protoReq *proto.ReadRequest,
) (*types.ReadResult, error) {
	resp, err := client.Read(ctx, protoReq)
	if err != nil {
		return nil, fmt.Errorf("gRPC Read failed: %w", err)
	}

	result := &types.ReadResult{
		MarkdownContent: resp.GetMarkdownContent(),
		ImageDirPath:    resp.GetImageDirPath(),
		Metadata:        resp.GetMetadata(),
		Error:           resp.GetError(),
	}
	if refs := resp.GetImageRefs(); len(refs) > 0 {
		result.ImageRefs = make([]types.ImageRef, 0, len(refs))
		for _, img := range refs {
			result.ImageRefs = append(result.ImageRefs, types.ImageRef{
				Filename:    img.GetFilename(),
				OriginalRef: img.GetOriginalRef(),
				MimeType:    img.GetMimeType(),
				StorageKey:  img.GetStorageKey(),
				ImageData:   img.GetImageData(),
			})

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Unwrap the error with errors.Unwrap or status.Convert(err) to see the real gRPC code and fix that root cause first
  2. Check docreader availability (kubectl get pods / service endpoint) and network reachability from the client
  3. 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
  4. Upgrade the docreader build to one that implements ReadStream so the unary fallback path is not used at all
  5. Verify TLS/auth env config used by docclient.LoadAuthConfigFromEnv matches the server's expectations

Example fix

// before
result, err := reader.Read(ctx, req)
if err != nil {
    return fmt.Errorf("parse failed: %w", err)
}
// after
result, err := reader.Read(ctx, req)
if err != nil {
    if st, ok := status.FromError(errors.Unwrap(err)); ok && st.Code() == codes.ResourceExhausted {
        return fmt.Errorf("document too large for legacy unary path (max %d MB); upgrade docreader to enable streaming", maxSizeMB)
    }
    return fmt.Errorf("parse failed: %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if !reader.IsConnected() {
    return fmt.Errorf("docreader gRPC client not connected")
}
// Keep documents under the unary limit since the fallback path is size-capped:
const maxUnaryBytes = 50 * 1024 * 1024 // or read MAX_FILE_SIZE_MB
if len(req.FileContent) > maxUnaryBytes {
    return fmt.Errorf("document %s exceeds unary fallback limit (%d bytes); streaming docreader required", req.FileName, maxUnaryBytes)
}

Try / catch

result, err := reader.Read(ctx, req)
if err != nil {
    var inner error = err
    for errors.Unwrap(inner) != nil { inner = errors.Unwrap(inner) }
    if st, ok := status.FromError(inner); ok {
        switch st.Code() {
        case codes.ResourceExhausted:
            return nil, fmt.Errorf("document too large for legacy unary path: %w", err)
        case codes.Unavailable:
            return nil, fmt.Errorf("docreader unavailable: %w", err) // retry with backoff
        default:
            return nil, fmt.Errorf("docreader read failed (%s): %w", st.Code(), err)
        }
    }
    return nil, err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/bc71fbac6693f28f. Report an issue: GitHub.