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

  1. Check the wrapped error: auth failures → refresh the access token; size errors → compress or split the audio
  2. Validate audio is non-empty before uploading
  3. Verify the converted format is really ogg/amr (extension matches content)
  4. 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

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


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/563272914f5661ac. Report an issue: GitHub.