{"record":{"id":"c0d76b95e0f8fd0e","repo":"chenhg5/cc-connect","slug":"decode-upload-response-w-body-s","errorCode":null,"errorMessage":"decode upload response: %w, body: %s","messagePattern":"decode upload response: %w, body: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/dingtalk/dingtalk.go","lineNumber":1394,"sourceCode":"\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"read upload response: %w\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"upload returned status %d: %s\", resp.StatusCode, respBody)\n\t}\n\n\tslog.Debug(\"dingtalk: media upload response\", \"status\", resp.StatusCode, \"body\", string(respBody))\n\n\tvar uploadResp struct {\n\t\tErrCode int    `json:\"errcode\"`\n\t\tErrMsg  string `json:\"errmsg\"`\n\t\tMediaID string `json:\"media_id\"`\n\t\tType    string `json:\"type\"`\n\t}\n\tif err := json.Unmarshal(respBody, &uploadResp); err != nil {\n\t\treturn \"\", fmt.Errorf(\"decode upload response: %w, body: %s\", err, respBody)\n\t}\n\n\tif uploadResp.ErrCode != 0 {\n\t\treturn \"\", fmt.Errorf(\"upload API error %d: %s\", uploadResp.ErrCode, uploadResp.ErrMsg)\n\t}\n\n\tif uploadResp.MediaID == \"\" {\n\t\treturn \"\", fmt.Errorf(\"empty media_id in upload response: %s\", respBody)\n\t}\n\n\tslog.Debug(\"dingtalk: media uploaded successfully\", \"media_id\", uploadResp.MediaID, \"type\", mediaType, \"size\", len(data))\n\treturn uploadResp.MediaID, nil\n}\n\nfunc (p *Platform) Stop() error {\n\tif p.streamCtxCancel != nil {\n\t\tp.streamCtxCancel()\n\t}","sourceCodeStart":1376,"sourceCodeEnd":1412,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/dingtalk/dingtalk.go#L1376-L1412","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nif err := json.Unmarshal(respBody, &uploadResp); err != nil {\n    return \"\", fmt.Errorf(\"decode upload response: %w, body: %s\", err, respBody)\n}\n// after\nif !json.Valid(respBody) {\n    return \"\", fmt.Errorf(\"decode upload response: non-JSON body (len=%d), body: %s\", len(respBody), respBody)\n}\nif err := json.Unmarshal(respBody, &uploadResp); err != nil {\n    return \"\", fmt.Errorf(\"decode upload response: %w, body: %s\", err, respBody)\n}","handlingStrategy":"try-catch","validationCode":"// caller-side: surface the body for diagnosis\nbody := err.Error() // includes ', body: ...'\nif !strings.Contains(body, \"errcode\") {\n    slog.Warn(\"upload response was not DingTalk JSON\", \"body\", body)\n}","typeGuard":"func isJSONResponse(b []byte) bool {\n    return json.Valid(b)\n}","tryCatchPattern":"mediaID, err := uploadMedia(ctx, data, mediaType)\nif err != nil {\n    if strings.Contains(err.Error(), \"decode upload response\") {\n        slog.Error(\"non-JSON upload response; check proxy/SSL interception\", \"detail\", err.Error())\n        return fmt.Errorf(\"dingtalk upload returned non-JSON: %w\", err)\n    }\n    return err\n}","preventionTips":["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."],"tags":["json","upload","api","dingtalk"],"backgroundTag":"invalid-json-response","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}