chenhg5/cc-connect · error
dingtalk: upload audio: %w
Error message
dingtalk: upload audio: %w
What it means
SendAudio uploads the (possibly converted) audio to DingTalk's media upload API via p.uploadMedia to obtain a mediaId; this error wraps any failure of that upload. Without a valid mediaId the voice message cannot be sent.
Source
Thrown at platform/dingtalk/dingtalk.go:1222
// Compress audio if too large (DingTalk limit is 2MB)
const maxAudioSize = 2 * 1024 * 1024
if len(audio) > maxAudioSize {
slog.Debug("dingtalk: audio too large, compressing", "size", len(audio), "max", maxAudioSize)
compressed, compressedFormat, err := p.compressAudio(ctx, audio, format)
if err != nil {
slog.Warn("dingtalk: compression failed, using original", "error", err)
} else {
audio = compressed
format = compressedFormat
slog.Debug("dingtalk: audio compressed", "new_size", len(audio), "new_format", format)
}
}
// Upload audio to DingTalk media API
mediaID, err := p.uploadMedia(ctx, audio, fmt.Sprintf("audio.%s", format), "voice")
if err != nil {
return fmt.Errorf("dingtalk: upload audio: %w", err)
}
slog.Debug("dingtalk: audio uploaded", "media_id", mediaID, "format", format, "size", len(audio))
// Calculate duration from audio size (rough estimate based on bitrate)
// NOTE: This is an approximation. For accurate duration, consider using ffprobe or go-audio library.
// OGG (Opus 64kbps): ~8KB/sec, AMR-NB (12.2kbps): ~4KB/sec, MP3 (128kbps): ~16KB/sec
var duration int
if format == "ogg" {
duration = len(audio) / 8000
} else if format == "amr" {
duration = len(audio) / 4000
} else if format == "mp3" {
duration = len(audio) / 16000
} else {
duration = len(audio) / 32000
}
if duration == 0 {View on GitHub (pinned to 4000b2338a)
Solutions
- Check the wrapped error: auth failures → refresh the access token; size errors → compress or split the audio
- Validate audio is non-empty before uploading
- Verify the converted format is really ogg/amr (extension matches content)
- Retry on transient network failures
Example fix
// before
mediaID, err := p.uploadMedia(ctx, audio, fmt.Sprintf("audio.%s", format), "voice")
// after
if len(audio) == 0 { return errors.New("dingtalk: empty audio, nothing to upload") }
mediaID, err := p.uploadMedia(ctx, audio, fmt.Sprintf("audio.%s", format), "voice") Defensive patterns
Strategy: retry
Validate before calling
if len(audio) == 0 { return errors.New("empty audio") }
if len(audio) > 20*1024*1024 { return errors.New("audio exceeds DingTalk media size limit") } Try / catch
var lastErr error
for i := 0; i < 3; i++ {
err := p.SendAudio(ctx, rc, audio, format)
if err == nil { return nil }
lastErr = err
if strings.Contains(err.Error(), "upload audio") { time.Sleep(time.Duration(1<<i) * time.Second); continue }
return err
}
return lastErr Prevention
- Validate audio size against DingTalk's media limits before upload
- Refresh tokens before upload if nearing expiry
- Use the correct media type ("voice") in uploadMedia calls
- Check the conversion output actually matches the declared format/extension
When it happens
Trigger: Calling SendAudio when uploadMedia fails: invalid/expired access token used by upload, network failure to the media endpoint, file exceeding DingTalk's media size limits, wrong media type parameter ("voice"), or empty audio bytes.
Common situations: Token service outage, audio payload larger than DingTalk's 20MB media limit, empty audio produced by a failed conversion upstream, transient network errors.
Understand the failure class
Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.
Related errors
- get download URL: %w
- http get: %w
- download returned status %d
- read response: %w
- empty downloadUrl in response
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/563272914f5661ac.
Report an issue: GitHub.