{"record":{"id":"ef489b03b8349f24","repo":"sipeed/picoclaw","slug":"wecom-upload-finish-returned-empty-media-id","errorCode":null,"errorMessage":"wecom upload finish returned empty media_id","messagePattern":"wecom upload finish returned empty media_id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/wecom/media.go","lineNumber":752,"sourceCode":"\t\t}\n\t}\n\n\tfinishEnv, err := c.sendCommandAck(wecomCommand{\n\t\tCmd:     wecomCmdUploadMediaEnd,\n\t\tHeaders: wecomHeaders{ReqID: randomID(10)},\n\t\tBody: wecomUploadMediaFinishBody{\n\t\t\tUploadID: initResp.UploadID,\n\t\t},\n\t}, wecomUploadTimeout)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfinishResp, err := decodeWeComEnvelopeBody[wecomUploadMediaFinishResponse](finishEnv)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif strings.TrimSpace(finishResp.MediaID) == \"\" {\n\t\treturn nil, fmt.Errorf(\"wecom upload finish returned empty media_id\")\n\t}\n\n\tuploaded := &wecomOutboundMedia{\n\t\tMsgType: kind,\n\t\tMediaID: finishResp.MediaID,\n\t}\n\tif kind == \"video\" {\n\t\tvideo := buildWeComVideoContent(finishResp.MediaID, filename, part.Caption)\n\t\tuploaded.Title = video.Title\n\t\tuploaded.Description = video.Description\n\t}\n\treturn uploaded, nil\n}\n\nfunc fallbackWeComMediaText(part bus.MediaPart, kind, filename string) string {\n\tvar lines []string\n\tif caption := strings.TrimSpace(part.Caption); caption != \"\" {\n\t\tlines = append(lines, caption)","sourceCodeStart":734,"sourceCodeEnd":770,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/wecom/media.go#L734-L770","documentation":"The WeCom chunked upload finished (all chunks sent, upload/finish acknowledged, response decoded) but the ack contained an empty media_id. Since media_id is required to reference the uploaded asset in the outbound message, the channel discards the result and errors out. Like error 662 this is a server/protocol anomaly, not bad input from the caller.","triggerScenarios":"A full uploadOutboundMedia cycle where wecomCmdUploadMediaFinish returns a well-formed envelope whose MediaID field is empty or whitespace — e.g. the server expired the upload session between the last chunk and finish, or the finish ack schema changed.","commonSituations":"Long-running uploads that straddle a gateway restart or session expiry; transient server-side storage failure; WeCom protocol update renaming the field; note the caller-side code at media.go:752 already falls back to a placeholder message, so users typically see the fallback rather than a hard failure unless the error propagates.","solutions":["Retry the media send once — the whole init/chunk/finish cycle restarts with a fresh upload_id","Keep individual attachments well under the 20MB cap so the finish command is not delayed by long chunk transfers","If it persists, dump the finish ack body and verify the wecomUploadMediaFinishResponse field mapping against the current WeCom protocol","Rely on the built-in placeholder fallback for user-facing delivery while investigating"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"null","typeGuard":"func isWecomEmptyMediaID(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"empty media_id\")\n}","tryCatchPattern":"if err := ch.Send(msg); err != nil {\n    if isWecomEmptyMediaID(err) {\n        // full init/chunk/finish restarts on retry; usually succeeds\n        time.Sleep(3 * time.Second)\n        err = ch.Send(msg)\n    }\n}","preventionTips":["Retry once after a short pause — server-side finish glitches are transient","Avoid max-size uploads that stretch the chunk phase across server maintenance windows","Rely on the channel's placeholder fallback for user-visible delivery while retrying"],"tags":["wecom","media","upload","protocol","server-error"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}