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

  1. 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).
  2. 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.
  3. Check for corporate proxies/gateways that return HTML error pages; add the proxy to allowlist or configure HTTP_PROXY correctly.
  4. 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.
  5. 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

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


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/1f912c92929032c5. Report an issue: GitHub.