chenhg5/cc-connect · error

decode upload response: %w, body: %s

Error message

decode upload response: %w, body: %s

What it means

The body returned by DingTalk's media upload endpoint could not be parsed as JSON (json.Unmarshal failed). The wrapped parse error and the raw body are both included to aid debugging. This indicates the response was not the expected JSON envelope (errcode/errmsg/media_id).

Source

Thrown at platform/dingtalk/dingtalk.go:1394

	respBody, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("read upload response: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("upload returned status %d: %s", resp.StatusCode, respBody)
	}

	slog.Debug("dingtalk: media upload response", "status", resp.StatusCode, "body", string(respBody))

	var uploadResp struct {
		ErrCode int    `json:"errcode"`
		ErrMsg  string `json:"errmsg"`
		MediaID string `json:"media_id"`
		Type    string `json:"type"`
	}
	if err := json.Unmarshal(respBody, &uploadResp); err != nil {
		return "", fmt.Errorf("decode upload response: %w, body: %s", err, respBody)
	}

	if uploadResp.ErrCode != 0 {
		return "", fmt.Errorf("upload API error %d: %s", uploadResp.ErrCode, uploadResp.ErrMsg)
	}

	if uploadResp.MediaID == "" {
		return "", fmt.Errorf("empty media_id in upload response: %s", respBody)
	}

	slog.Debug("dingtalk: media uploaded successfully", "media_id", uploadResp.MediaID, "type", mediaType, "size", len(data))
	return uploadResp.MediaID, nil
}

func (p *Platform) Stop() error {
	if p.streamCtxCancel != nil {
		p.streamCtxCancel()
	}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Log/inspect the body portion of the error message — it shows exactly what came back instead of JSON.
  2. Check for proxy, VPN, or SSL-inspection middleware altering the response; bypass or allowlist oapi.dingtalk.com.
  3. Verify the upload URL is the current DingTalk media upload endpoint (media/upload with correct access_token and type params).
  4. If the body looks like a DingTalk error rendered as text, check DingTalk Open Platform docs/changelog for API changes.
  5. Retry the request — a truncated response can be transient.

Example fix

// before
if err := json.Unmarshal(respBody, &uploadResp); err != nil {
    return "", fmt.Errorf("decode upload response: %w, body: %s", err, respBody)
}
// after
if !json.Valid(respBody) {
    return "", fmt.Errorf("decode upload response: non-JSON body (len=%d), body: %s", len(respBody), respBody)
}
if err := json.Unmarshal(respBody, &uploadResp); err != nil {
    return "", fmt.Errorf("decode upload response: %w, body: %s", err, respBody)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// caller-side: surface the body for diagnosis
body := err.Error() // includes ', body: ...'
if !strings.Contains(body, "errcode") {
    slog.Warn("upload response was not DingTalk JSON", "body", body)
}

Type guard

func isJSONResponse(b []byte) bool {
    return json.Valid(b)
}

Try / catch

mediaID, err := uploadMedia(ctx, data, mediaType)
if err != nil {
    if strings.Contains(err.Error(), "decode upload response") {
        slog.Error("non-JSON upload response; check proxy/SSL interception", "detail", err.Error())
        return fmt.Errorf("dingtalk upload returned non-JSON: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Media upload returns HTTP 200 but with a non-JSON body — e.g. an HTML error page from an intercepting proxy/CAPTCHA gateway, a truncated/garbled response, or hitting a wrong URL that serves plain text.

Common situations: SSL-inspecting corporate proxies injecting HTML into 200 responses; DNS hijacking or captive portals; hitting a deprecated/moved upload endpoint that no longer returns JSON; response body truncated by an intermediary.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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