{"record":{"id":"ae788c5e1a2cc247","repo":"chenhg5/cc-connect","slug":"read-full-body-w","errorCode":null,"errorMessage":"read full body: %w","messagePattern":"read full body: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/feishu/resource_download.go","lineNumber":173,"sourceCode":"\tcase http.StatusPartialContent:\n\t\tcr := resp.Header.Get(\"Content-Range\")\n\t\ttotal, ok := parseContentRangeTotal(cr)\n\t\tif !ok {\n\t\t\treturn nil, 0, fmt.Errorf(\"first-chunk: 206 without parseable Content-Range %q\", cr)\n\t\t}\n\t\tbody, err := io.ReadAll(io.LimitReader(resp.Body, 1))\n\t\tif err != nil {\n\t\t\treturn nil, 0, fmt.Errorf(\"read first-chunk body: %w\", err)\n\t\t}\n\t\treturn body, total, nil\n\n\tcase http.StatusOK:\n\t\t// Server ignored Range and sent the full body. We deliberately\n\t\t// honour it and skip the chunked loop — this matches pre-#1741\n\t\t// behaviour for files small enough that Feishu doesn't truncate.\n\t\tbody, err := io.ReadAll(io.LimitReader(resp.Body, p.resourceMaxBytes+1))\n\t\tif err != nil {\n\t\t\treturn nil, 0, fmt.Errorf(\"read full body: %w\", err)\n\t\t}\n\t\tif int64(len(body)) > p.resourceMaxBytes {\n\t\t\treturn nil, 0, fmt.Errorf(\"resource too large: body exceeds cap %d\", p.resourceMaxBytes)\n\t\t}\n\t\treturn body, 0, nil\n\n\tdefault:\n\t\treturn nil, 0, fmt.Errorf(\"first-chunk: unexpected status %d\", resp.StatusCode)\n\t}\n}\n\n// resourceFetchRemainingChunks loops Range GETs starting after the first\n// byte, concatenates them with `first`, and verifies the total size matches\n// what the probe advertised.\nfunc (p *Platform) resourceFetchRemainingChunks(ctx context.Context, token, messageID, fileKey, resType string, total int64, first []byte) ([]byte, error) {\n\tbuf := bytes.NewBuffer(make([]byte, 0, total))\n\tbuf.Write(first)\n","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/feishu/resource_download.go#L155-L191","documentation":"Wraps an io.ReadAll failure while reading a full 200 body (server ignored Range) up to resourceMaxBytes+1 bytes in resourceFetchFirstChunk. Same class of transport failure as the first-chunk read error but on the whole-body path; it can also fire when the download context is cancelled while streaming a large body.","triggerScenarios":"Server returned 200 with the full body and the stream breaks mid-read: connection reset, TLS truncation, parent context cancelled, or read deadline hit on a large slow transfer.","commonSituations":"Downloading large files over unstable networks, NAT/firewall idle timeouts dropping long-lived connections, or user-triggered cancellation (message superseded) aborting the stream.","solutions":["Retry the download once connectivity is stable","Check for idle-connection killing middleboxes; raise keepalive/idle timeouts","Ensure callers don't cancel the context prematurely for large files"],"exampleFix":"null","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"read full body\") {\n    select {\n    case <-ctx.Done(): return ctx.Err()\n    case <-time.After(retryDelay):\n        return retryDownload(ctx, msgID, key, resType)\n    }\n}","preventionTips":["Don't cancel the download context while a large file is still streaming unless truly superseded","Keep connections alive long enough for the largest expected file over the slowest expected link","Check middlebox idle-timeout settings for long transfers","Prefer the chunked path succeeding (Range honoured) over huge single 200 bodies"],"tags":["go","feishu","network","io","timeout"],"backgroundTag":"network-request-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}