chenhg5/cc-connect · error
wecom: decode send response: %w
Error message
wecom: decode send response: %w
What it means
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.
Source
Thrown at platform/wecom/wecom.go:616
}
body, _ := json.Marshal(payload)
apiURL := p.wecomAPIURL("/cgi-bin/message/send", url.Values{
"access_token": []string{accessToken},
})
resp, err := p.apiClient.Post(apiURL, "application/json", strings.NewReader(string(body)))
if err != nil {
return fmt.Errorf("wecom: send markdown: %w", err)
}
defer resp.Body.Close()
var result struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("wecom: decode send response: %w", err)
}
if result.ErrCode != 0 {
return fmt.Errorf("wecom: send markdown failed: %d %s", result.ErrCode, result.ErrMsg)
}
return nil
}
func (p *Platform) sendText(accessToken, toUser, text string) error {
payload := map[string]any{
"touser": toUser,
"msgtype": "text",
"agentid": p.agentID,
"text": map[string]string{"content": text},
"safe": 0,
}
body, _ := json.Marshal(payload)
apiURL := p.wecomAPIURL("/cgi-bin/message/send", url.Values{View on GitHub (pinned to 4000b2338a)
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).
Example fix
// before
var result struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return fmt.Errorf("wecom: decode send response: %w", err)
}
// after
body, readErr := io.ReadAll(resp.Body)
if readErr != nil {
return fmt.Errorf("wecom: read send response: status=%d: %w", resp.StatusCode, readErr)
}
var result struct {
ErrCode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
if err := json.Unmarshal(body, &result); err != nil {
return fmt.Errorf("wecom: decode send response: status=%d body=%q: %w", resp.StatusCode, core.RedactToken(string(body)), err)
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before calling Reply/sendMarkdown, verify the endpoint will answer with JSON:
resp, err := http.Get(baseURL) // or HEAD/GET on the configured base URL
if err != nil { /* unreachable */ }
ct := resp.Header.Get("Content-Type")
if !strings.Contains(ct, "application/json") {
// misconfigured base URL or intercepting proxy
} Type guard
func isDecodeSendResponseErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "wecom: decode send response")
} Try / catch
if err := p.Reply(msg, "markdown text"); err != nil {
var netErr net.Error
if errors.As(err, &netErr) {
// transient network issue: retry with backoff
} else if isDecodeSendResponseErr(err) {
slog.Warn("wecom returned non-JSON response; check base URL/proxy", "err", err)
} else {
slog.Error("wecom reply failed", "err", err)
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- wecom-ws: ack timeout
- send permission request: %w
- read permission response: %w
- decode response: %w
- decode response: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/1f912c92929032c5.
Report an issue: GitHub.