{"record":{"id":"8a33df39fa3b9113","repo":"chenhg5/cc-connect","slug":"http-d-8a33df","errorCode":null,"errorMessage":"HTTP %d","messagePattern":"HTTP %d","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/max/max.go","lineNumber":1198,"sourceCode":"// for image/file payloads are already authenticated, so no bot token is\n// attached to the request.\nfunc (p *Platform) downloadAttachment(ctx context.Context, url string) ([]byte, string, error) {\n\tif url == \"\" {\n\t\treturn nil, \"\", fmt.Errorf(\"empty url\")\n\t}\n\tdlCtx, cancel := context.WithTimeout(ctx, attachmentDownloadTO)\n\tdefer cancel()\n\treq, err := http.NewRequestWithContext(dlCtx, http.MethodGet, url, nil)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tresp, err := p.client.Do(req)\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, \"\", fmt.Errorf(\"HTTP %d\", resp.StatusCode)\n\t}\n\tdata, err := io.ReadAll(io.LimitReader(resp.Body, maxAttachmentBytes+1))\n\tif err != nil {\n\t\treturn nil, \"\", err\n\t}\n\tif len(data) > maxAttachmentBytes {\n\t\treturn nil, \"\", fmt.Errorf(\"attachment exceeds %d bytes\", maxAttachmentBytes)\n\t}\n\treturn data, resp.Header.Get(\"Content-Type\"), nil\n}\n\n// resolveMediaURL asks MAX for the playable/downloadable URL of a video or\n// audio attachment. MAX delivers only an opaque token in the message payload\n// and exposes /videos/{token} and /audios/{token} for resolution.\nfunc (p *Platform) resolveMediaURL(ctx context.Context, kind, token string) (string, string, error) {\n\tif token == \"\" {\n\t\treturn \"\", \"\", fmt.Errorf(\"empty token\")\n\t}","sourceCodeStart":1180,"sourceCodeEnd":1216,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/max/max.go#L1180-L1216","documentation":"downloadAttachment got a non-200 status from the CDN/MAX attachment endpoint. Unlike the poll path, the response body (which usually holds the error detail) is discarded and only the status code is reported.","triggerScenarios":"The pre-signed URL expired, was revoked, or the CDN returned 403/404/5xx; the URL is reachable but the signature no longer authenticates the download.","commonSituations":"Downloading an attachment long after the message was received (pre-signed links expire); MAX CDN outage; token revoked by sender deleting the media.","solutions":["Download attachments promptly after receiving the message, before the pre-signed link expires","Log respBody on non-200 to see the CDN error detail","Re-fetch a fresh URL via resolveMediaURL and retry once on 403/404","Check whether the attachment was deleted upstream (404) and degrade gracefully"],"exampleFix":"// before\nif resp.StatusCode != http.StatusOK {\n\treturn nil, \"\", fmt.Errorf(\"HTTP %d\", resp.StatusCode)\n}\n// after\nif resp.StatusCode != http.StatusOK {\n\tbody, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\treturn nil, \"\", fmt.Errorf(\"HTTP %d: %s\", resp.StatusCode, body)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"data, _, err := p.downloadAttachment(ctx, url)\nif err != nil && strings.HasPrefix(err.Error(), \"HTTP 5\") {\n\ttime.Sleep(backoff); retry()\n}","preventionTips":["Download attachments immediately on receipt","Treat 403/404 as permanent and surface 'media unavailable' to users","Log the response body on non-200 for diagnosis"],"tags":["network","http","attachments"],"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"}