{"record":{"id":"2228026c282b7be9","repo":"Tencent/WeKnora","slug":"failed-to-read-zhipu-response-w","errorCode":null,"errorMessage":"failed to read Zhipu response: %w","messagePattern":"failed to read Zhipu response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/infrastructure/web_search/zhipu.go","lineNumber":220,"sourceCode":"\t\treturn time.Time{}, false\n\t}\n\tfor _, layout := range []string{\n\t\ttime.RFC3339Nano,\n\t\t\"2006-01-02 15:04:05\",\n\t\t\"2006-01-02 15:04\",\n\t\t\"2006-01-02\",\n\t} {\n\t\tif parsed, err := time.Parse(layout, value); err == nil {\n\t\t\treturn parsed, true\n\t\t}\n\t}\n\treturn time.Time{}, false\n}\n\nfunc readZhipuResponseBody(reader io.Reader) ([]byte, error) {\n\tbody, err := io.ReadAll(io.LimitReader(reader, maxZhipuResponseBytes+1))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read Zhipu response: %w\", err)\n\t}\n\tif len(body) > maxZhipuResponseBytes {\n\t\treturn nil, fmt.Errorf(\"Zhipu response exceeds %d bytes\", maxZhipuResponseBytes)\n\t}\n\treturn body, nil\n}\n\nfunc zhipuHTTPError(statusCode int, body []byte) error {\n\tvar response zhipuSearchResponse\n\tif err := json.Unmarshal(body, &response); err == nil && (response.Error.Code != \"\" || response.Error.Message != \"\") {\n\t\treturn fmt.Errorf(\"Zhipu API returned status %d (%s): %s\", statusCode, response.Error.Code, response.Error.Message)\n\t}\n\tdetail := strings.TrimSpace(string(body))\n\tif len(detail) > 4096 {\n\t\tdetail = detail[:4096]\n\t}\n\tif detail == \"\" {\n\t\treturn fmt.Errorf(\"Zhipu API returned status %d\", statusCode)","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/infrastructure/web_search/zhipu.go#L202-L238","documentation":"readZhipuResponseBody fails when io.ReadAll on the (rate-limited) response body errors — the connection dropped or the stream broke mid-read. The error is wrapped with %w so the underlying io error is preserved.","triggerScenarios":"Calling Search() when the Zhipu server closes the connection before the body completes, or the client/context is cancelled during body read.","commonSituations":"Flaky mobile/VPN networks; server-side timeouts on slow responses; LBs terminating idle keep-alive connections mid-transfer.","solutions":["Retry the request — mid-stream read failures are usually transient","Increase client timeout if large responses are expected on slow links","Disable aggressive keep-alive idle timeouts that kill in-flight connections","Check intermediate proxies/load balancer idle timeout settings"],"exampleFix":"// before\nbody, err := readZhipuResponseBody(resp.Body)\nif err != nil { return nil, err }\n// after\nbody, err := readZhipuResponseBody(resp.Body)\nif err != nil {\n    if errors.Is(err, io.ErrUnexpectedEOF) { return retry(ctx, req) }\n    return nil, err\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"func isBodyReadError(err error) bool { return strings.Contains(err.Error(), \"failed to read Zhipu response\") }","tryCatchPattern":"results, err := provider.Search(ctx, q)\nif err != nil && errors.Is(err, io.ErrUnexpectedEOF) {\n    return retryWithBackoff(ctx, q) // mid-stream disconnect\n}","preventionTips":["Retry idempotent search reads on transient io errors","Avoid overly aggressive LB idle timeouts","Set adequate client timeouts for slow links","Limit concurrent connections to the provider"],"tags":["io","network","zhipu","web-search"],"backgroundTag":"connection-reset","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}