{"record":{"id":"90315e3acbdbd2ba","repo":"chenhg5/cc-connect","slug":"cdn-upload-w","errorCode":null,"errorMessage":"cdn upload: %w","messagePattern":"cdn upload: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/max/max.go","lineNumber":596,"sourceCode":"\t\treturn \"\", err\n\t}\n\tif _, err := fw.Write(data); err != nil {\n\t\treturn \"\", err\n\t}\n\tif err := mw.Close(); err != nil {\n\t\treturn \"\", err\n\t}\n\n\tcdnReq, err := http.NewRequestWithContext(uploadCtx, http.MethodPost, urlInfo.URL, &buf)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tp.setAuth(cdnReq)\n\tcdnReq.Header.Set(\"Content-Type\", mw.FormDataContentType())\n\n\tcdnResp, err := p.uploadClient.Do(cdnReq)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cdn upload: %w\", err)\n\t}\n\tdefer cdnResp.Body.Close()\n\tif cdnResp.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(io.LimitReader(cdnResp.Body, 512))\n\t\treturn \"\", fmt.Errorf(\"cdn upload: HTTP %d: %s\", cdnResp.StatusCode, body)\n\t}\n\tcdnBody, err := io.ReadAll(io.LimitReader(cdnResp.Body, 64*1024))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"read cdn response: %w\", err)\n\t}\n\t// MAX CDN uses different response shapes per attachment kind:\n\t//   image: {\"photos\": {\"<photo_id>\": {\"token\": \"...\"}}}\n\t//   file:  {\"token\": \"...\"}\n\t//   video/audio: \"<retval>1</retval>\" (XML) — the real token is already in urlInfo.Token\n\tif token := extractCDNToken(kind, cdnBody); token != \"\" {\n\t\treturn token, nil\n\t}\n\tif urlInfo.Token != \"\" {","sourceCodeStart":578,"sourceCodeEnd":614,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/max/max.go#L578-L614","documentation":"This is the transport-level failure branch of the second step of MAX's two-step attachment upload: the multipart/form-data POST of the file bytes to the presigned CDN URL. When p.uploadClient.Do returns a non-nil error (connection failure, TLS error, DNS failure, timeout, context cancellation), the error is wrapped with the \"cdn upload: \" prefix and returned. It is a network/client-side failure, not an HTTP status error.","triggerScenarios":"Calling SendImage/SendFile/SendAudio when the CDN endpoint is unreachable, the 5-minute attachmentUploadTO deadline or caller context expires mid-upload, TLS handshake fails, DNS resolution fails, or the connection is reset while streaming the multipart body for large files.","commonSituations":"Uploading large files over slow links hitting the 5-minute timeout; firewall/proxy blocking the CDN host returned by /uploads; transient CDN outages; IPv6 connectivity issues; mobile/offline networks; canceling the enclosing session context.","solutions":["Check network reachability of the CDN URL host returned by /uploads (curl -v to it) — proxies and firewalls commonly block it.","Reduce attachment size or increase attachmentUploadTO if large files routinely time out on your network.","Confirm the caller's context isn't already near expiry; the upload inherits the session context deadline.","Retry the upload with backoff — CDN transport failures are often transient.","If behind a corporate proxy, configure the uploadClient's Proxy/Transport to route through it."],"exampleFix":"// before\nerr := p.uploadAttachment(ctx, \"image\", data, \"photo.png\")\n// after (pre-flight size check + bounded retry)\nconst maxUpload = 50 << 20 // 50 MiB\nif len(data) > maxUpload {\n\treturn fmt.Errorf(\"attachment too large: %d bytes\", len(data))\n}\nvar token string\nvar lastErr error\nfor attempt := 0; attempt < 3; attempt++ {\n\ttoken, lastErr = p.uploadAttachment(ctx, \"image\", data, \"photo.png\")\n\tif lastErr == nil {\n\t\tbreak\n\t}\n\tvar netErr net.Error\n\tif errors.As(lastErr, &netErr) && netErr.Timeout() {\n\t\ttime.Sleep(time.Duration(attempt+1) * time.Second)\n\t\tcontinue\n\t}\n\tbreak\n}","handlingStrategy":"retry","validationCode":"// Reachability pre-check before uploading\nfunc cdnReachable(uploadClient *http.Client, url string) bool {\n\tu, err := neturl.Parse(url)\n\tif err != nil || u.Host == \"\" { return false }\n\tconn, err := net.DialTimeout(\"tcp\", u.Host, 3*time.Second)\n\tif err != nil { return false }\n\tconn.Close()\n\treturn true\n}","typeGuard":"var _ net.Error\nfunc isTimeoutOrCancel(err error) bool {\n\treturn errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) || (func() bool { var n net.Error; return errors.As(err, &n) && n.Timeout() })()\n}","tryCatchPattern":"token, err := p.uploadAttachment(ctx, kind, data, filename)\nif err != nil {\n\tvar nerr net.Error\n\tif errors.As(err, &nerr) && nerr.Timeout() {\n\t\t// bounded retry with backoff for transient transport failures\n\t}\n\tif errors.Is(err, context.Canceled) {\n\t\treturn fmt.Errorf(\"max: upload canceled: %w\", err)\n\t}\n}","preventionTips":["Size-limit attachments client-side before upload; large files are the top timeout cause.","Use wired/stable networks for bulk uploads; check proxy/firewall rules for the CDN host.","Keep the caller's context alive long enough to cover the 5-minute upload window.","Add exponential backoff retries around SendImage/SendFile/SendAudio."],"tags":["network","upload","timeout","max"],"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-14T05:17:10.506Z"}