chenhg5/cc-connect · error

weixin: sendMessage: response json: %w: %s

Error message

weixin: sendMessage: response json: %w: %s

What it means

After sendMessage posts to the WeChat API it decodes the response body into sendMessageResp. This error wraps a json.Unmarshal failure and appends a 256-byte snippet of the raw body. Non-JSON replies (HTML error pages, truncated bodies) trigger it.

Source

Thrown at platform/weixin/client.go:180

func (c *apiClient) sendMessage(ctx context.Context, msg *sendMessageReq) error {
	if msg == nil {
		return fmt.Errorf("weixin: sendMessage: nil request")
	}
	msg.BaseInfo = baseInfo{ChannelVersion: channelVersion}
	payload, err := json.Marshal(msg)
	if err != nil {
		return err
	}
	raw, err := c.post(ctx, "ilink/bot/sendmessage", payload, 0, "sendMessage")
	if err != nil {
		return err
	}
	if len(bytes.TrimSpace(raw)) == 0 {
		return nil
	}
	var resp sendMessageResp
	if err := json.Unmarshal(raw, &resp); err != nil {
		return fmt.Errorf("weixin: sendMessage: response json: %w: %s", err, truncateForLog(raw, 256))
	}
	if resp.Ret != 0 {
		slog.Warn("weixin: sendMessage declined by API",
			"ret", resp.Ret, "errcode", resp.Errcode, "errmsg", resp.Errmsg,
			"content_len", len(payload))
		return fmt.Errorf("weixin: sendMessage: ret=%d errcode=%d errmsg=%s",
			resp.Ret, resp.Errcode, resp.Errmsg)
	}
	return nil
}

func (c *apiClient) getUploadURL(ctx context.Context, req getUploadURLRequest) (*getUploadURLResponse, error) {
	req.BaseInfo = baseInfo{ChannelVersion: channelVersion}
	payload, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}
	raw, err := c.post(ctx, "ilink/bot/getuploadurl", payload, 0, "getUploadUrl")

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the appended raw snippet (%s) to identify what came back
  2. Retry the send; transient gateway bodies resolve on retry
  3. Check WeChat iLink API changelog and update sendMessageResp if fields changed
  4. Bypass/inspect proxies that inject HTML into responses
Defensive patterns

Strategy: try-catch

Validate before calling

if len(bytes.TrimSpace(raw)) == 0 { return nil } // already handled; check snippet in error for HTML

Type guard

func looksLikeHTML(b []byte) bool { return bytes.Contains(bytes.ToLower(b[:min(64, len(b))]), []byte("<html")) }

Try / catch

if err := c.sendMessage(ctx, msg); err != nil {
	if strings.Contains(err.Error(), "response json") {
		slog.Warn("weixin: non-JSON send response; will retry", "err", err)
		time.Sleep(backoff)
		return retrySend()
	}
	return err
}

Prevention

When it happens

Trigger: The send endpoint returns a non-JSON or truncated body: gateway 502 HTML, connection cut mid-response, or an iLink API schema change that makes the body fail to decode into sendMessageResp.

Common situations: Corporate proxy returning HTML; WeChat API outage or protocol change; payload so large the response is truncated; running an old client against a new API.

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/5e22e6f1e82b760f. Report an issue: GitHub.