chenhg5/cc-connect · error
decode response: %w
Error message
decode response: %w
What it means
After getDownloadURL receives a 200 response from the DingTalk media download API, it decodes the JSON body into a downloadResponse struct. This error wraps any JSON decoding failure — malformed body, wrong content type (e.g. HTML error page behind a proxy), or an unexpected field layout.
Source
Thrown at platform/dingtalk/dingtalk.go:713
return "", fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-acs-dingtalk-access-token", token)
resp, err := p.httpClient.Do(req)
if err != nil {
return "", fmt.Errorf("do request: %w", err)
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("api returned status %d", resp.StatusCode)
}
var result downloadResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", fmt.Errorf("decode response: %w", err)
}
if result.DownloadUrl == "" {
return "", fmt.Errorf("empty downloadUrl in response")
}
return result.DownloadUrl, nil
}
func (p *Platform) getAccessToken() (string, error) {
p.tokenMu.Lock()
defer p.tokenMu.Unlock()
// Return cached token if still valid
if p.accessToken != "" && time.Now().Before(p.tokenExpiry) {
return p.accessToken, nil
}
View on GitHub (pinned to 4000b2338a)
Solutions
- Log the raw response body (before decoding) to see what DingTalk actually returned.
- If the body is HTML, a proxy/gateway is interfering — bypass or configure the proxy for api.dingtalk.com.
- Verify the DingTalk API schema: the field must be downloadUrl (camelCase) in the response.
- Retry once on transient decode failures, then surface the error to the user chat.
- Ensure resp.Body isn't already consumed by an earlier io.ReadAll in the same request path.
Example fix
// before: silent about body shape
var result downloadResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", fmt.Errorf("decode response: %w", err)
}
// after: capture raw body for diagnosis
raw, _ := io.ReadAll(resp.Body)
var result downloadResponse
if err := json.Unmarshal(raw, &result); err != nil {
return "", fmt.Errorf("decode response %q: %w", string(raw), err)
} Defensive patterns
Strategy: try-catch
Try / catch
url, err := p.getDownloadURL(ctx, code)
if err != nil {
if strings.Contains(err.Error(), "decode response") {
logRawBodyForDiagnosis() // or retry once
}
return err
} Prevention
- Ensure api.dingtalk.com is reachable without proxy interference (NO_PROXY / bypass rules).
- Log raw bodies on decode failure for postmortems.
- Don't consume resp.Body twice in the call path.
- Watch DingTalk API changelogs for response schema changes.
When it happens
Trigger: handleImageMessage / handleFileMessage / downloadAudio hit this when the download API returns HTTP 200 but a body that is not valid JSON or does not match the expected {"downloadUrl": ...} shape — commonly an HTML error page from a gateway, truncated response, or a changed API schema.
Common situations: Corporate proxy or CDN intercepting and returning an HTML login/captcha page, DingTalk returning an empty 200 body, Go version mismatch irrelevant but schema drift in DingTalk API responses, or TLS interception appliances re-writing responses.
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
- marshal request: %w
- do request: %w
- api returned status %d
- decode upload response: %w, body: %s
- send permission request: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/79e12e0e348efe37.
Report an issue: GitHub.