{"record":{"id":"975a73849548210c","repo":"chenhg5/cc-connect","slug":"get-download-url-w","errorCode":null,"errorMessage":"get download URL: %w","messagePattern":"get download URL: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"platform/dingtalk/dingtalk.go","lineNumber":646,"sourceCode":"\t\t\tsenderStaffId:  data.SenderStaffId,\n\t\t\tmessageID:      data.MsgId,\n\t\t\tisGroup:        data.ConversationType == \"2\",\n\t\t},\n\t\tFiles: []core.FileAttachment{{\n\t\t\tMimeType: mimeType,\n\t\t\tData:     fileBytes,\n\t\t\tFileName: fileName,\n\t\t}},\n\t}\n\n\tp.handler(p, msg)\n}\n\nfunc (p *Platform) downloadAudio(downloadCode string) ([]byte, string, error) {\n\t// Get download URL\n\tdownloadURL, err := p.getDownloadURL(downloadCode)\n\tif err != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"get download URL: %w\", err)\n\t}\n\n\t// Download audio file\n\tresp, err := p.httpClient.Get(downloadURL)\n\tif err != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"http get: %w\", err)\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, \"\", fmt.Errorf(\"download returned status %d\", resp.StatusCode)\n\t}\n\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, \"\", fmt.Errorf(\"read response: %w\", err)\n\t}\n","sourceCodeStart":628,"sourceCodeEnd":664,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/platform/dingtalk/dingtalk.go#L628-L664","documentation":"downloadAudio wraps any failure from getDownloadURL with this message. The underlying cause is produced by getDownloadURL (token acquisition, request marshalling, HTTP errors), and this wrapper marks the 'obtain download URL' stage of the audio download pipeline.","triggerScenarios":"handleAudioMessage received a voice message and called downloadAudio(downloadCode); the inner getDownloadURL failed for any reason: expired/missing access token, network failure, or DingTalk API returning a non-200 status for /v1.0/robot/messageFiles/download.","commonSituations":"Expired or revoked access token; DingTalk API outage; invalid downloadCode forwarded from the webhook payload; robot code mismatch causing API rejection; network/DNS issues in the host environment.","solutions":["Inspect the wrapped cause (%w) in logs — fix the root error (token, network, or API status).","Verify client_id/client_secret are valid so getAccessToken succeeds.","Retry the message handling if the cause was transient (5xx/network); DingTalk download URLs are short-lived.","Check that the robotCode used matches the robot that received the message."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Before handling media, verify credentials work:\nif err := validateDingtalkCredentials(clientID, clientSecret); err != nil { log.Fatal(err) }","typeGuard":null,"tryCatchPattern":"if err != nil {\n    var cause string\n    if unwrapped := errors.Unwrap(err); unwrapped != nil { cause = unwrapped.Error() }\n    log.Warn(\"dingtalk audio unavailable\", \"cause\", cause)\n    reply(\"Sorry, I could not fetch the voice message.\")\n}","preventionTips":["Keep credentials valid so the token stage never fails","Log the full wrapped error chain (%w cause) to find the root stage","Handle media failures gracefully in the messaging UI instead of crashing"],"tags":["dingtalk","audio","api","error-wrapping"],"backgroundTag":"http-error-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"}