{"record":{"id":"424dcbc116cfbc29","repo":"chenhg5/cc-connect","slug":"resource-api-status-d-body-q","errorCode":null,"errorMessage":"resource API status=%d body=%q","messagePattern":"resource API status=(.+?) body=%q","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/feishu/resource_download.go","lineNumber":269,"sourceCode":"// for small files and as a fallback when the size probe fails. We honour\n// resourceMaxBytes via Content-Length + body cap so a misbehaving server\n// can't blow up memory.\nfunc (p *Platform) resourceSingleGet(ctx context.Context, token, messageID, fileKey, resType string) ([]byte, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, p.resourceURL(messageID, fileKey, resType), nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"build request: %w\", err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+token)\n\n\tresp, err := p.resourceDownloadHTTP.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"resource request: %w\", err)\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\tbody, _ := io.ReadAll(io.LimitReader(resp.Body, 4*1024))\n\t\treturn nil, fmt.Errorf(\"resource API status=%d body=%q\", resp.StatusCode, strings.TrimSpace(string(body)))\n\t}\n\n\tif cl := resp.ContentLength; cl > p.resourceMaxBytes {\n\t\treturn nil, fmt.Errorf(\"resource too large: Content-Length=%d exceeds cap %d\", cl, p.resourceMaxBytes)\n\t}\n\n\t// LimitReader caps the body too in case the server lies about\n\t// Content-Length; we read up to cap+1 bytes to detect the lie.\n\tdata, err := io.ReadAll(io.LimitReader(resp.Body, p.resourceMaxBytes+1))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read resource: %w\", err)\n\t}\n\tif int64(len(data)) > p.resourceMaxBytes {\n\t\treturn nil, fmt.Errorf(\"resource too large: body exceeds cap %d\", p.resourceMaxBytes)\n\t}\n\tslog.Debug(p.tag()+\": resource downloaded (single GET)\",\n\t\t\"file_key\", fileKey, \"type\", resType, \"size\", len(data))\n\treturn data, nil","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/feishu/resource_download.go#L251-L287","documentation":"The Feishu resource API responded with a non-2xx HTTP status code during a single-GET download. The library reads up to 4 KiB of the response body and includes it in the error so the API's error message (e.g. invalid token, file not found, permission denied) is visible to the developer.","triggerScenarios":"resourceSingleGet receives a status outside 200-299 from the resource endpoint — expired/invalid tenant_access_token (401/99991663-style errors), wrong message_id or file_key (404), missing im:resource permission, or Feishu server error (5xx).","commonSituations":"Token expired or fetched for the wrong tenant/app; file_key copied from a message in another chat or an expired resource; app lacks the im:message/im:resource scopes; Feishu incident causing 5xx; rate limiting returning 429.","solutions":["Read the body quoted in the error — Feishu's code/msg fields identify the exact cause (invalid token, not found, permission)","Refresh the tenant_access_token and ensure the app has im:resource / im:message:readonly scopes","Verify message_id and file_key belong to the same message and that the bot is a member of the chat","For 5xx/429, retry with backoff; for 401, re-authenticate; for 404, ask the sender to re-share the file","Check that the token type matches the API (tenant_access_token vs user_access_token)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// keep token fresh and scopes granted before downloading\ntoken, err := p.ensureFreshTenantToken(ctx)\nif err != nil { return nil, fmt.Errorf(\"auth for resource download: %w\", err) }","typeGuard":null,"tryCatchPattern":"data, err := p.resourceSingleGet(ctx, token, msgID, fileKey, resType)\nif err != nil {\n    if strings.Contains(err.Error(), \"resource API status=\") {\n        // parse status + Feishu code from the quoted body:\n        // 401/99991663 -> refresh token; 404 -> re-request file; 5xx -> backoff retry\n        return classifyFeishuAPIError(err)\n    }\n    return err\n}","preventionTips":["Refresh tenant_access_token proactively before expiry","Grant the app im:resource / im:message:readonly scopes in the Feishu developer console","Keep the bot in the chat whose message resources you download","Apply backoff on 429/5xx instead of immediate retries"],"tags":["http","api","feishu","non-2xx"],"backgroundTag":"http-error-response","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}