{"record":{"id":"9889c8d407f02463","repo":"sipeed/picoclaw","slug":"decode-wecom-response-body-w","errorCode":null,"errorMessage":"decode wecom response body: %w","messagePattern":"decode wecom response body: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/channels/wecom/media.go","lineNumber":665,"sourceCode":"\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)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read media file: %w\", err)\n\t}","sourceCodeStart":647,"sourceCodeEnd":683,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/channels/wecom/media.go#L647-L683","documentation":"json.Unmarshal of the upload ack body failed (media.go:664-665). The ack envelope had bytes, but they are not the expected JSON shape: an HTML/text error page, a schema that changed (renamed or retyped fields), or a UTF-8 BOM in front of the JSON. The %w preserves *json.SyntaxError / *json.UnmarshalTypeError for precise diagnosis.","triggerScenarios":"upload init/finish ack body is HTML or plain text; server sends {\"errcode\":...,\"errmsg\":...} instead of the expected upload_id/media_id shape; number-vs-string type drift after a gateway version change; BOM-prefixed JSON.","commonSituations":"Custom WeCom gateways fronting the real API; version skew between picoclaw and the bridge; gateways that wrap errors in the ack body instead of the envelope status.","solutions":["Log env.Body verbatim (it is short) - the content immediately identifies HTML vs wrong-schema vs BOM","errors.As(*json.SyntaxError) means non-JSON; *json.UnmarshalTypeError means schema drift - compare fields against wecomUploadMediaInitResponse/FinishResponse","Align picoclaw and gateway versions","If the body is an errcode/errmsg error envelope, surface that error instead of the generic decode failure"],"exampleFix":"// before: decode any body shape\nif err := json.Unmarshal(env.Body, &out); err != nil {\n    return out, fmt.Errorf(\"decode wecom response body: %w\", err)\n}\n\n// after: recognize gateway error envelopes first\nvar probe struct {\n    ErrCode int    `json:\"errcode\"`\n    ErrMsg  string `json:\"errmsg\"`\n}\nif json.Unmarshal(env.Body, &probe) == nil && probe.ErrCode != 0 {\n    return out, fmt.Errorf(\"wecom api error %d: %s\", probe.ErrCode, probe.ErrMsg)\n}\nenv.Body = bytes.TrimPrefix(env.Body, []byte(\"\\xef\\xbb\\xbf\")) // strip BOM\nif err := json.Unmarshal(env.Body, &out); err != nil {\n    return out, fmt.Errorf(\"decode wecom response body: %w (body=%q)\", err, env.Body)\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isJSONErr(err error) bool {\n\tvar se *json.SyntaxError\n\tvar te *json.UnmarshalTypeError\n\treturn errors.As(err, &se) || errors.As(err, &te)\n}","tryCatchPattern":"var synErr *json.SyntaxError\nvar typErr *json.UnmarshalTypeError\n\nswitch {\ncase errors.As(err, &synErr):\n    // body was not JSON at all - dump it and check gateway health\n    log.Printf(\"non-JSON ack body: %q\", rawBody)\ncase errors.As(err, &typErr):\n    // schema drift: field %s got %s, expected %s\n    log.Printf(\"schema mismatch: %s\", typErr.Error())\ndefault:\n    log.Printf(\"decode failed: %v\", err)\n}","preventionTips":["pin compatible channel/gateway versions; upgrade together","strip UTF-8 BOMs before unmarshalling gateway responses","log ack bodies on decode failure - it turns a mystery into a one-line diagnosis"],"tags":["json","protocol","upload","versioning"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}