{"record":{"id":"1f912c92929032c5","repo":"chenhg5/cc-connect","slug":"wecom-decode-send-response-w","errorCode":null,"errorMessage":"wecom: decode send response: %w","messagePattern":"wecom: decode send response: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/wecom/wecom.go","lineNumber":616,"sourceCode":"\t}\n\n\tbody, _ := json.Marshal(payload)\n\tapiURL := p.wecomAPIURL(\"/cgi-bin/message/send\", url.Values{\n\t\t\"access_token\": []string{accessToken},\n\t})\n\n\tresp, err := p.apiClient.Post(apiURL, \"application/json\", strings.NewReader(string(body)))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"wecom: send markdown: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tvar result struct {\n\t\tErrCode int    `json:\"errcode\"`\n\t\tErrMsg  string `json:\"errmsg\"`\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn fmt.Errorf(\"wecom: decode send response: %w\", err)\n\t}\n\tif result.ErrCode != 0 {\n\t\treturn fmt.Errorf(\"wecom: send markdown failed: %d %s\", result.ErrCode, result.ErrMsg)\n\t}\n\treturn nil\n}\n\nfunc (p *Platform) sendText(accessToken, toUser, text string) error {\n\tpayload := map[string]any{\n\t\t\"touser\":  toUser,\n\t\t\"msgtype\": \"text\",\n\t\t\"agentid\": p.agentID,\n\t\t\"text\":    map[string]string{\"content\": text},\n\t\t\"safe\":    0,\n\t}\n\n\tbody, _ := json.Marshal(payload)\n\tapiURL := p.wecomAPIURL(\"/cgi-bin/message/send\", url.Values{","sourceCodeStart":598,"sourceCodeEnd":634,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/wecom/wecom.go#L598-L634","documentation":"This error wraps the failure to JSON-decode the response body of the WeCom (WeChat Work) message/send API call made by sendMarkdown. The library POSTs the markdown payload to /cgi-bin/message/send and expects a JSON body containing errcode/errmsg; if the body is not valid JSON (or is empty/truncated), json.Decoder returns an error which is wrapped here with the %w verb so the underlying cause (e.g. 'unexpected end of JSON input') is preserved. It signals that the HTTP request itself may have succeeded at the transport level, but the reply could not be interpreted, so delivery status is unknown.","triggerScenarios":"sendMarkdown (invoked via Reply) calls apiClient.Post to /cgi-bin/message/send and then json.NewDecoder(resp.Body).Decode(&result). This error is returned when Decode fails: the response body is empty, truncated by a proxy/gateway, is HTML (e.g. an error page from a misconfigured wecomAPIURL or captive portal), or the connection was reset mid-body read.","commonSituations":"1) webhook/proxy (nginx, corporate gateway) intercepting the request and returning an HTML 502 page; 2) wrong base URL in config pointing at a non-WeCom endpoint that returns non-JSON; 3) network flakiness causing a truncated response body; 4) an intermediate layer (e.g. a mock server or a wrong port) returning plain text or empty body.","solutions":["Log resp.StatusCode and a snippet of the raw body before decoding to see what the server actually returned (dump resp.Body via io.ReadAll into a buffer, then json.Unmarshal).","Verify the configured WeCom API base URL (wecomAPIURL target /cgi-bin/message/send) points to https://qyapi.weixin.qq.com or your approved proxy, not an arbitrary host.","Check for corporate proxies/gateways that return HTML error pages; add the proxy to allowlist or configure HTTP_PROXY correctly.","Retry the send on transient decode failures (truncated body usually indicates a network hiccup); inspect the wrapped error via errors.Unwrap for the concrete cause.","If behind a custom reverse proxy, ensure it does not buffer/strip the JSON response (Content-Type application/json, no compression issues)."],"exampleFix":"// before\nvar result struct {\n    ErrCode int    `json:\"errcode\"`\n    ErrMsg  string `json:\"errmsg\"`\n}\nif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n    return fmt.Errorf(\"wecom: decode send response: %w\", err)\n}\n// after\nbody, readErr := io.ReadAll(resp.Body)\nif readErr != nil {\n    return fmt.Errorf(\"wecom: read send response: status=%d: %w\", resp.StatusCode, readErr)\n}\nvar result struct {\n    ErrCode int    `json:\"errcode\"`\n    ErrMsg  string `json:\"errmsg\"`\n}\nif err := json.Unmarshal(body, &result); err != nil {\n    return fmt.Errorf(\"wecom: decode send response: status=%d body=%q: %w\", resp.StatusCode, core.RedactToken(string(body)), err)\n}","handlingStrategy":"try-catch","validationCode":"// Before calling Reply/sendMarkdown, verify the endpoint will answer with JSON:\nresp, err := http.Get(baseURL) // or HEAD/GET on the configured base URL\nif err != nil { /* unreachable */ }\nct := resp.Header.Get(\"Content-Type\")\nif !strings.Contains(ct, \"application/json\") {\n    // misconfigured base URL or intercepting proxy\n}","typeGuard":"func isDecodeSendResponseErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"wecom: decode send response\")\n}","tryCatchPattern":"if err := p.Reply(msg, \"markdown text\"); err != nil {\n    var netErr net.Error\n    if errors.As(err, &netErr) {\n        // transient network issue: retry with backoff\n    } else if isDecodeSendResponseErr(err) {\n        slog.Warn(\"wecom returned non-JSON response; check base URL/proxy\", \"err\", err)\n    } else {\n        slog.Error(\"wecom reply failed\", \"err\", err)\n    }\n}","preventionTips":["Pin the WeCom API base URL to https://qyapi.weixin.qq.com in config and validate it at startup with a smoke request.","Set an explicit http.Client.Timeout so truncated/hung responses fail fast with a clearer error.","Buffer the response body (io.ReadAll) and log status + redacted body on decode failure for diagnosability.","Deploy behind allowlisted egress so intermediaries cannot inject HTML error pages."],"tags":["network","json","wecom","http-response"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}