{"record":{"id":"f81431359a407abb","repo":"chenhg5/cc-connect","slug":"range-chunk-retries-exhausted","errorCode":null,"errorMessage":"range chunk retries exhausted","messagePattern":"range chunk retries exhausted","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/feishu/resource_download.go","lineNumber":379,"sourceCode":"\t\t_ = resp.Body.Close()\n\n\t\tif resp.StatusCode >= 500 && resp.StatusCode < 600 && attempt < maxTransientRetries {\n\t\t\tlastErr = fmt.Errorf(\"range status %d body=%q\", resp.StatusCode, strings.TrimSpace(string(body)))\n\t\t\tcontinue\n\t\t}\n\n\t\tif resp.StatusCode >= 200 && resp.StatusCode < 300 && end-start+1 == int64(len(body)) {\n\t\t\t// Server ignored Range and returned a 200 with exactly the bytes\n\t\t\t// we wanted — fine for the last chunk of a file, slightly off for\n\t\t\t// non-tail chunks. Caller decides; we return what we got.\n\t\t\treturn body, nil\n\t\t}\n\n\t\treturn nil, fmt.Errorf(\"range request status=%d body=%q\", resp.StatusCode, strings.TrimSpace(string(body)))\n\t}\n\n\tif lastErr == nil {\n\t\tlastErr = errors.New(\"range chunk retries exhausted\")\n\t}\n\treturn nil, fmt.Errorf(\"range chunk retries exhausted: %w\", lastErr)\n}\n\n// defaultResourceChunkSize returns the chunk size used when a Platform is\n// constructed without going through newPlatform (tests, manual fixtures).\n// Matches the production default of 8 MiB.\nfunc defaultResourceChunkSize() int64 { return 8 * 1024 * 1024 }\n\n// fetchResourceTokenOrDefault returns the bearer token used for resource\n// downloads. Tests can inject a stub via Platform.fetchResourceToken;\n// production callers fall through to fetchFreshTenantAccessToken which uses\n// the lark SDK to mint a fresh tenant token on demand.\nfunc (p *Platform) fetchResourceTokenOrDefault(ctx context.Context) (string, error) {\n\tif p.fetchResourceToken != nil {\n\t\treturn p.fetchResourceToken(ctx)\n\t}\n\treturn p.fetchFreshTenantAccessToken(ctx)","sourceCodeStart":361,"sourceCodeEnd":397,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/feishu/resource_download.go#L361-L397","documentation":"resourceRangeChunk performs HTTP Range requests to fetch one chunk of a large resource, retrying on failure. When every attempt for a chunk fails (or the retry loop exits with no recorded error), it wraps the last error in 'range chunk retries exhausted', signaling that the chunk could not be downloaded after all retries. Callers (resourceFetchRemainingChunks) surface this as the download failing for the resource.","triggerScenarios":"Raised when the Range GET repeatedly returns non-2xx statuses (e.g. status=416 for out-of-range offsets, 5xx server errors), the connection drops mid-chunk, or the loop's attempt counter is exhausted while lastErr remains nil.","commonSituations":"Transferring large files over flaky networks; servers that do not properly support Range requests and return 200/416; CDN timeouts on large offsets; source file changing size between the initial request and chunk fetches (416).","solutions":["Retry the whole resource download later — transient network/server errors are the most common cause.","Check that the HTTP server serving the resource supports Range requests (Accept-Ranges: bytes) and honors If-Range/ETag semantics.","Reduce chunk size or concurrency if the server throttles or drops large/range-heavy transfers.","Inspect the wrapped lastErr in the message (e.g. 'range request status=416') to address the underlying cause specifically."],"exampleFix":"// before (server without range support)\nresp to 'Range: bytes=...' returns 200 full-body -> chunk parse fails each retry\n// after\nserve with Accept-Ranges: bytes and honor Range headers, or download whole file without chunking","handlingStrategy":"retry","validationCode":"resp, err := http.Head(resourceURL)\nif err != nil || !strings.Contains(resp.Header.Get(\"Accept-Ranges\"), \"bytes\") {\n    log.Warn(\"server may not support Range requests; chunked download will likely fail\", \"url\", resourceURL)\n}","typeGuard":null,"tryCatchPattern":"data, err := resourceDownload(ctx, url)\nif strings.Contains(err.Error(), \"retries exhausted\") {\n    // surface wrapped cause and back off before retriggering\n    log.Error(\"resource download failed after retries\", \"cause\", err, \"retry_in\", 5*time.Minute)\n    time.Sleep(5 * time.Minute)\n    data, err = resourceDownload(ctx, url)\n}","preventionTips":["Confirm the source server supports Range requests (Accept-Ranges: bytes)","Download on stable networks; avoid concurrent chunking against rate-limited hosts","Keep the source file immutable (stable size/ETag) during transfer to avoid 416s","Inspect the wrapped cause ('range request status=...') rather than only the exhaustion message"],"tags":["network","http","retry","download","range-request"],"backgroundTag":"request-timeout","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"}