{"record":{"id":"b88c7d4a35d7d2c1","repo":"sipeed/picoclaw","slug":"unsupported-wecom-media-type-or-size-for-q","errorCode":null,"errorMessage":"unsupported wecom media type or size for %q","messagePattern":"unsupported wecom media type or size for %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/wecom/media.go","lineNumber":687,"sourceCode":"\nfunc (c *WeComChannel) uploadOutboundMedia(\n\tctx context.Context,\n\tlocalPath, filename, contentType string,\n\tpart bus.MediaPart,\n) (*wecomOutboundMedia, error) {\n\t_ = ctx\n\n\tcontentType = detectLocalWeComContentType(localPath, contentType)\n\tfilename = ensureWeComOutboundFilename(filename, localPath, contentType)\n\n\tdata, err := os.ReadFile(localPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read media file: %w\", err)\n\t}\n\tsize := int64(len(data))\n\tkind := outboundWeComMediaKind(part.Type, filename, contentType, size)\n\tif kind == \"\" {\n\t\treturn nil, fmt.Errorf(\"unsupported wecom media type or size for %q\", filename)\n\t}\n\n\ttotalChunks := (len(data) + wecomUploadChunkMaxBytes - 1) / wecomUploadChunkMaxBytes\n\tif totalChunks <= 0 || totalChunks > wecomUploadMaxChunks {\n\t\treturn nil, fmt.Errorf(\"wecom upload requires 1-%d chunks, got %d\", wecomUploadMaxChunks, totalChunks)\n\t}\n\n\tsum := md5.Sum(data)\n\tinitEnv, err := c.sendCommandAck(wecomCommand{\n\t\tCmd:     wecomCmdUploadMediaInit,\n\t\tHeaders: wecomHeaders{ReqID: randomID(10)},\n\t\tBody: wecomUploadMediaInitBody{\n\t\t\tType:        kind,\n\t\t\tFilename:    filename,\n\t\t\tTotalSize:   size,\n\t\t\tTotalChunks: totalChunks,\n\t\t\tMD5:         hex.EncodeToString(sum[:]),\n\t\t},","sourceCodeStart":669,"sourceCodeEnd":705,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/wecom/media.go#L669-L705","documentation":"Thrown by the WeCom channel's chunked media uploader when outboundWeComMediaKind() cannot classify the file into one of the supported upload kinds (image, voice, video, file). Classification fails when the file is smaller than 5 bytes, larger than the per-kind cap (image 2MB, voice 2MB, video 10MB, file 20MB), or the part type/content-type/extension matches none of the allowed media types. The error is raised before any network call, right after os.ReadFile of the resolved local file.","triggerScenarios":"Sending an outbound message with a media part where: part.Type is not file/image/audio/voice/video and no fallback applies; the content-type or file extension is not in the WeCom allowlist; the file exceeds wecomOutboundMediaMaxBytes (20MB) as a generic file or the per-kind limit; or the file is under 5 bytes (wecomUploadMinBytes). Concretely, any Send with parts whose resolved local file fails the size/size-kind matrix in outboundWeComMediaKind (pkg/channels/wecom/media.go:575).","commonSituations":"Bot replies with a generated image larger than 2MB; a TTS voice clip exceeds 2MB; a video attachment exceeds 10MB; an arbitrary document exceeds 20MB; an empty or 2-byte placeholder file is referenced; the attachment has an unusual extension with a generic application/octet-stream content type so no kind matches.","solutions":["Check the file size against WeCom limits before sending: image/voice <= 2MB, video <= 10MB, any other file <= 20MB, minimum 5 bytes","Re-encode or downscale the media (compress images, lower video bitrate, transcode audio) until it fits the per-kind cap","Force part.Type to \"file\" for documents that do not need image/voice/video treatment, as long as they are <= 20MB","Verify the resolved filename/content-type: give the attachment a proper extension (.jpg/.png/.mp3/.mp4 etc.) so content-type detection succeeds","If the media cannot be shrunk, send a text/link reference instead of the binary"],"exampleFix":"// before\npart := channels.MediaPart{Type: \"image\", Ref: imgRef} // img is 3.5MB -> error 660\n\n// after\npart := channels.MediaPart{Type: \"image\", Ref: shrunkImgRef} // re-encoded to <=2MB\n// or downgrade to a plain file part if it is within 20MB\npart := channels.MediaPart{Type: \"file\", Ref: imgRef}","handlingStrategy":"validation","validationCode":"// before send: classify locally with the same limits the uploader enforces\nfunc wecomMediaKindOK(partType, filename, contentType string, size int64) error {\n    if size < 5 {\n        return fmt.Errorf(\"file too small: %d bytes (min 5)\", size)\n    }\n    switch strings.ToLower(partType) {\n    case \"image\", \"audio\", \"voice\":\n        if size > 2<<20 {\n            return fmt.Errorf(\"%s exceeds 2MB\", partType)\n        }\n    case \"video\":\n        if size > 10<<20 {\n            return fmt.Errorf(\"video exceeds 10MB\")\n        }\n    default:\n        if size > 20<<20 {\n            return fmt.Errorf(\"file exceeds 20MB\")\n        }\n    }\n    return nil\n}\n\ninfo, _ := os.Stat(localPath)\nif err := wecomMediaKindOK(part.Type, filename, contentType, info.Size()); err != nil {\n    // re-encode, downscale, or drop the part instead of sending\n}","typeGuard":"func isWecomUnsupportedMedia(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"unsupported wecom media type or size\")\n}","tryCatchPattern":"if uploaded, err := ch.Send(msg); err != nil {\n    if isWecomUnsupportedMedia(err) {\n        // do not retry: size/type is deterministic. Downgrade to text or re-encode.\n    }\n}","preventionTips":["Validate media size against 2MB image/voice, 10MB video, 20MB file caps before queueing","Normalize filenames to carry a proper extension so content-type detection works","Compress generated images/videos before attaching them to replies","Reject or skip sub-5-byte placeholder files at generation time"],"tags":["wecom","media","upload","validation","size-limit"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}