{"record":{"id":"3f470c790b71d7eb","repo":"Tencent/WeKnora","slug":"read-poll-response-body-w","errorCode":null,"errorMessage":"read poll response body: %w","messagePattern":"read poll response body: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/infrastructure/docparser/mineru_cloud_converter.go","lineNumber":283,"sourceCode":"\turl := fmt.Sprintf(\"%s/extract-results/batch/%s\", c.baseURL, batchID)\n\thttpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor k, v := range headers {\n\t\thttpReq.Header.Set(k, v)\n\t}\n\n\tclient := utils.NewSSRFSafeHTTPClient(utils.SSRFSafeHTTPClientConfig{Timeout: 30 * time.Second, MaxRedirects: 5})\n\tresp, err := client.Do(httpReq)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read poll response body: %w\", err)\n\t}\n\n\tvar pollResp batchPollResponse\n\tif err := json.Unmarshal(respBody, &pollResp); err != nil {\n\t\treturn nil, fmt.Errorf(\"decode poll response: %w\", err)\n\t}\n\tif pollResp.Code != 0 {\n\t\treturn nil, fmt.Errorf(\"poll error code=%d msg=%s\", pollResp.Code, pollResp.Msg)\n\t}\n\n\tif len(pollResp.Data.ExtractResult) == 0 {\n\t\treturn nil, nil\n\t}\n\n\t// Dump the raw extract_result JSON for debugging\n\trawExtract := string(pollResp.Data.ExtractResult)\n\tif len(rawExtract) > 4000 {\n\t\tlogger.Infof(context.Background(), \"[MinerUCloud] Raw extract_result (truncated to 4000 chars): %s ...\", rawExtract[:4000])","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/infrastructure/docparser/mineru_cloud_converter.go#L265-L301","documentation":"This error wraps a failure to read the HTTP response body while polling the MinerU Cloud batch status endpoint in fetchBatchStatus (internal/infrastructure/docparser/mineru_cloud_converter.go:283). After the HTTP request succeeds, io.ReadAll(resp.Body) can still fail if the connection is reset, times out mid-body, or the server closes the stream early. The library surfaces the underlying io error wrapped with %w so the root cause (e.g. unexpected EOF, connection reset) is preserved.","triggerScenarios":"Calling pollBatchResult -> fetchBatchStatus when the MinerU Cloud server accepts the request headers but the TCP stream breaks before the full JSON body arrives: network interruption, proxy/LB idle timeout, server crash mid-response, or response body truncated by a Content-Length mismatch.","commonSituations":"Long-running batch polls over flaky VPN or corporate proxies that kill idle/long responses; container DNS or NAT timeouts dropping keep-alive connections; MinerU Cloud returning a very large poll response that gets cut off on slow links.","solutions":["Retry the poll request: fetchBatchStatus is called repeatedly by pollBatchResult, so a transient read failure usually resolves on the next poll interval.","Check network path stability (proxy/firewall) between the host and the MinerU Cloud API endpoint.","Inspect the wrapped error (errors.Unwrap / %v of the cause) to confirm whether it is 'unexpected EOF', 'connection reset by peer', or a client timeout, and tune the HTTP client timeout accordingly.","If it persists, verify the MinerU Cloud service status or endpoint URL configuration."],"exampleFix":"// before\nclient := &http.Client{Timeout: 10 * time.Second}\n// after\n// increase timeout / add retry in the polling loop\nclient := &http.Client{Timeout: 60 * time.Second}\n// in pollBatchResult:\n// for attempt := 0; attempt < 3; attempt++ {\n//     resp, err := fetchBatchStatus(ctx, id)\n//     if err == nil { break }\n//     time.Sleep(pollInterval)\n// }","handlingStrategy":"retry","validationCode":"if resp.Body == nil {\n    return errors.New(\"poll response has no body\")\n}\nif resp.ContentLength == 0 {\n    return errors.New(\"poll response body is empty\")\n}","typeGuard":null,"tryCatchPattern":"items, err := pollBatchResult(ctx, batchID)\nif err != nil {\n    var netErr net.Error\n    if errors.As(err, &netErr) || errors.Is(err, io.ErrUnexpectedEOF) {\n        // transient: retry the poll after the interval\n        time.Sleep(pollInterval)\n        return pollBatchResult(ctx, batchID)\n    }\n    return fmt.Errorf(\"poll failed permanently: %w\", err)\n}","preventionTips":["Set a generous HTTP client timeout so long poll responses aren't cut mid-body.","Retry transient read failures inside the existing polling loop instead of failing the whole batch.","Monitor proxy/LB idle timeouts between the host and MinerU Cloud."],"tags":["network","http","go","io","polling"],"backgroundTag":"http-response-body-read-failed","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}