{"record":{"id":"595fff6fd6121c57","repo":"sipeed/picoclaw","slug":"wecom-response-body-is-empty","errorCode":null,"errorMessage":"wecom response body is empty","messagePattern":"wecom response body is empty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/wecom/media.go","lineNumber":662,"sourceCode":"\nfunc buildWeComVideoContent(mediaID, filename, description string) *wecomVideoContent {\n\ttitle := strings.TrimSuffix(filename, filepath.Ext(filename))\n\ttitle = trimWeComBytes(title, 64)\n\tif title == \"\" {\n\t\ttitle = \"video\"\n\t}\n\tdescription = trimWeComBytes(description, 512)\n\treturn &wecomVideoContent{\n\t\tMediaID:     mediaID,\n\t\tTitle:       title,\n\t\tDescription: description,\n\t}\n}\n\nfunc decodeWeComEnvelopeBody[T any](env wecomEnvelope) (T, error) {\n\tvar out T\n\tif len(env.Body) == 0 {\n\t\treturn out, fmt.Errorf(\"wecom response body is empty\")\n\t}\n\tif err := json.Unmarshal(env.Body, &out); err != nil {\n\t\treturn out, fmt.Errorf(\"decode wecom response body: %w\", err)\n\t}\n\treturn out, nil\n}\n\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)","sourceCodeStart":644,"sourceCodeEnd":680,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/wecom/media.go#L644-L680","documentation":"decodeWeComEnvelopeBody got an ack envelope with a zero-length Body (media.go:659-662). It is used for the chunked upload protocol's init and finish acks (media.go:710,747): sendCommandAck returned successfully, but the reply envelope carried no JSON payload to decode into wecomUploadMediaInitResponse/FinishResponse.","triggerScenarios":"The WeCom gateway/bridge acknowledged the upload init (or finish) command but omitted the body: transient gateway hiccup, protocol/version drift where the server answers ack-only, or an error path that drops the payload while keeping the envelope.","commonSituations":"Upgrading one side of the picoclaw WeCom bridge without the other; gateway restarts mid-upload; custom gateways that never send bodies on certain commands.","solutions":["Retry the upload once from scratch (new upload_id) - empty-body acks are usually transient","If persistent: dump the raw envelope (cmd, req_id, body length) in debug logs and compare against the expected protocol","Align versions of the channel and gateway/bridge components","Check whether an error ack was sent separately and swallowed; correlate by ReqID"],"exampleFix":"// before: single attempt, empty body kills the send\ninitEnv, err := c.sendCommandAck(cmd, wecomUploadTimeout)\ninitResp, err := decodeWeComEnvelopeBody[wecomUploadMediaInitResponse](initEnv)\n\n// after: one bounded retry on the empty-body case\nvar initResp wecomUploadMediaInitResponse\nfor attempt := 0; attempt < 2; attempt++ {\n    initEnv, err := c.sendCommandAck(cmd, wecomUploadTimeout)\n    if err != nil { return nil, err }\n    initResp, err = decodeWeComEnvelopeBody[wecomUploadMediaInitResponse](initEnv)\n    if err == nil || !strings.Contains(err.Error(), \"body is empty\") { break }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"var errEmptyBody = errors.New(\"wecom response body is empty\")\n\n// bounded single retry for transient empty-body acks\nfunc uploadWithRetry(ctx context.Context, do func() error) error {\n    if err := do(); err == nil {\n        return nil\n    } else if !errors.Is(err, errEmptyBody) && !strings.Contains(err.Error(), \"body is empty\") {\n        return err\n    }\n    time.Sleep(500 * time.Millisecond)\n    return do()\n}","preventionTips":["log ReqID with every upload command so acks can be correlated","upgrade channel and gateway together","treat empty-body acks as retriable, but alarm if the rate climbs"],"tags":["protocol","upload","wecom","json"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}