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
- Log/inspect the body portion of the error message — it shows exactly what came back instead of JSON.
- Check for proxy, VPN, or SSL-inspection middleware altering the response; bypass or allowlist oapi.dingtalk.com.
- Verify the upload URL is the current DingTalk media upload endpoint (media/upload with correct access_token and type params).
- If the body looks like a DingTalk error rendered as text, check DingTalk Open Platform docs/changelog for API changes.
- 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
- Allowlist DingTalk API domains in proxies/SSL-inspection appliances.
- Verify endpoint URLs against current DingTalk docs after API version changes.
- Log full response bodies on parse failures for postmortems.
- Use direct egress for server hosts to avoid captive-portal interference.
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
- marshal request: %w
- decode response: %w
- upload returned status %d: %s
- upload API error %d: %s
- empty media_id in upload response: %s
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/c0d76b95e0f8fd0e.
Report an issue: GitHub.