{"record":{"id":"480aff0747607a7c","repo":"chenhg5/cc-connect","slug":"wecom-ws-send-image-w","errorCode":null,"errorMessage":"wecom-ws: send image: %w","messagePattern":"wecom-ws: send image: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/wecom/websocket_outbound_media.go","lineNumber":36,"sourceCode":"\twecomWSUploadMaxChunks = 100\n)\n\n// SendImage uploads and sends an image through the WeCom AI Bot WebSocket API.\nfunc (p *WSPlatform) SendImage(ctx context.Context, rctx any, img core.ImageAttachment) error {\n\trc, ok := rctx.(wsReplyContext)\n\tif !ok {\n\t\treturn fmt.Errorf(\"wecom-ws: SendImage: invalid reply context type %T\", rctx)\n\t}\n\tif rc.chatID == \"\" {\n\t\treturn fmt.Errorf(\"wecom-ws: chatID is empty, cannot send image\")\n\t}\n\tif len(img.Data) == 0 {\n\t\treturn fmt.Errorf(\"wecom-ws: image data is empty\")\n\t}\n\n\tmediaID, err := p.uploadWSMedia(ctx, \"image\", wsImageFileName(img), img.Data)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"wecom-ws: send image: %w\", err)\n\t}\n\tif err := p.sendWSMediaMessage(ctx, rc.chatID, \"image\", mediaID); err != nil {\n\t\treturn fmt.Errorf(\"wecom-ws: send image: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc (p *WSPlatform) uploadWSMedia(ctx context.Context, mediaType, filename string, data []byte) (string, error) {\n\ttotalChunks := (len(data) + wecomWSUploadChunkSize - 1) / wecomWSUploadChunkSize\n\tif totalChunks == 0 {\n\t\treturn \"\", fmt.Errorf(\"empty media data\")\n\t}\n\tif totalChunks > wecomWSUploadMaxChunks {\n\t\treturn \"\", fmt.Errorf(\"media too large: %d chunks exceeds maximum %d\", totalChunks, wecomWSUploadMaxChunks)\n\t}\n\n\tsum := md5.Sum(data)\n\tinitReqID := p.generateReqID(\"aibot_upload_media_init\")","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/wecom/websocket_outbound_media.go#L18-L54","documentation":"This is the wrapped error returned when the chunked media upload step (uploadWSMedia, which performs aibot_upload_media_init/chunk/finish frames over the WeCom AI Bot WebSocket) fails while sending an image. The original cause is preserved via %w; typical causes include socket not connected, request timeouts waiting for a response frame, oversized media (>100 chunks / ~50MB), or WeCom API error responses.","triggerScenarios":"Calling SendImage with valid context and data, but uploadWSMedia returns an error: WebSocket connection is down or closed, req_id response never arrives (timeout), totalChunks exceeds wecomWSUploadMaxChunks (100 x 512KB), or WeCom returns an error body in the upload finish frame.","commonSituations":"Sending large images near the 50MB chunk limit; unstable networks or the WeCom WS connection dropped mid-upload; calling SendImage before the platform finished connecting; WeCom rejecting the media (bad md5/base64 encoding or service-side limits).","solutions":["Unwrap the error (errors.Unwrap / %v of the chain) to see the root cause and address it specifically.","Verify the WSPlatform WebSocket connection is established and healthy before sending; re-send after reconnect.","Reduce image size below the 100-chunk (~50MB) upload limit or compress before sending.","Retry transient timeouts; if a specific req_id never gets a response, check WeCom AI Bot API status and response handling in uploadWSMedia."],"exampleFix":"// before\nif err := platform.SendImage(ctx, rc, bigImg); err != nil { return err } // opaque\n// after\nif err := platform.SendImage(ctx, rc, img); err != nil {\n    return fmt.Errorf(\"send failed: %w\", err) // log the unwrapped cause\n}","handlingStrategy":"retry","validationCode":"const maxWSMediaChunks = 100\nif len(img.Data) > maxWSMediaChunks*512*1024 {\n    return errors.New(\"image exceeds WeCom WS upload limit (~50MB)\")\n}\nif !platform.IsConnected() {\n    return errors.New(\"websocket not connected\")\n}","typeGuard":null,"tryCatchPattern":"err := platform.SendImage(ctx, rctx, img)\nfor i := 0; i < 3 && err != nil; i++ {\n    if errors.Is(err, context.DeadlineExceeded) || isNetErr(err) {\n        time.Sleep(backoff(i))\n        err = platform.SendImage(ctx, rctx, img)\n    } else { break }\n}","preventionTips":["Compress/resize images before sending to stay under the 100-chunk (~50MB) limit.","Confirm the WebSocket connection is healthy before initiating media sends.","Use context timeouts so a hung upload fails fast and can be retried.","Log the unwrapped root cause (%v of the chain) to distinguish timeouts from API errors."],"tags":["go","wecom","media-upload","websocket","wrapped-error"],"backgroundTag":"api-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"}