chenhg5/cc-connect · error

max: upload audio: %w

Error message

max: upload audio: %w

What it means

SendAudio first uploads the audio blob to MAX via uploadAttachment — a two-step process (POST /uploads?type=audio to get an upload URL, then multipart POST of the bytes) — and wraps any failure with "max: upload audio: %w". It means the audio never reached MAX's storage, so no message was sent. The underlying error carries the real cause: an HTTP error from the uploads endpoint, a network/timeout failure, or empty audio data.

Source

Thrown at platform/max/max.go:482

		}},
	}
	return p.postMessage(ctx, rctx.chatID, body)
}

// SendAudio implements core.AudioSender — uploads a voice/audio blob and sends
// it as a native MAX audio attachment. Used by the TTS pipeline to reply in
// voice when [tts] is enabled in config.
func (p *Platform) SendAudio(ctx context.Context, replyCtx any, audio []byte, format string) error {
	rctx, ok := replyCtx.(replyContext)
	if !ok {
		return fmt.Errorf("max: unexpected replyCtx type %T", replyCtx)
	}
	if format == "" {
		format = "mp3"
	}
	token, err := p.uploadAttachment(ctx, "audio", audio, "voice."+format)
	if err != nil {
		return fmt.Errorf("max: upload audio: %w", err)
	}
	body := &maxSendBody{
		Attachments: []maxOutAttachment{{
			Type:    "audio",
			Payload: maxTokenPayload{Token: token},
		}},
	}
	return p.postMessage(ctx, rctx.chatID, body)
}

// UpdateMessage implements core.MessageUpdater via PUT /messages?message_id=.
func (p *Platform) UpdateMessage(ctx context.Context, replyCtx any, content string) error {
	rctx, ok := replyCtx.(replyContext)
	if !ok {
		return fmt.Errorf("max: unexpected replyCtx type %T", replyCtx)
	}
	if rctx.messageID == "" {
		return fmt.Errorf("max: update message: no message id in reply context")

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Inspect the wrapped cause with errors.Unwrap / %v of the returned error to see whether it was a timeout, an HTTP status, or empty data, and fix that root cause first.
  2. Verify the MAX bot token in config.toml is valid — an auth failure surfaces here as a non-200 from /uploads.
  3. Check network/proxy access to the MAX API host from the machine running cc-connect; retry transient failures.
  4. Guard the input: check len(audio) > 0 and that the TTS step succeeded before calling SendAudio.
  5. For large files, ensure the context passed to SendAudio has enough headroom (the platform allows up to 5 minutes).

Example fix

// before
if err := p.SendAudio(ctx, rctx, ttsOut, "opus"); err != nil {
    slog.Error("tts send failed", "err", err)
}

// after
if len(ttsOut) == 0 {
    return fmt.Errorf("tts produced no audio")
}
ctx, cancel := context.WithTimeout(ctx, 5*time.Minute)
defer cancel()
if err := p.SendAudio(ctx, rctx, ttsOut, "opus"); err != nil {
    var httpErr interface{ HTTPCode() int }
    if errors.As(err, &httpErr) {
        slog.Error("max upload rejected", "err", err) // auth/API problem
    } else if errors.Is(err, context.DeadlineExceeded) {
        slog.Error("audio upload timed out", "bytes", len(ttsOut))
    }
    return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

if len(audio) == 0 {
    return fmt.Errorf("no audio data to send")
}
if format == "" { format = "mp3" } // mirror the platform default

Try / catch

err := platform.SendAudio(ctx, replyCtx, audio, format)
if err != nil {
    switch {
    case errors.Is(err, context.DeadlineExceeded):
        // retry with a fresh, longer-lived context
    case strings.Contains(err.Error(), "HTTP "):
        // API rejected the upload: check token/config
    case strings.Contains(err.Error(), "empty attachment data"):
        // caller bug: check TTS output
    }
    return fmt.Errorf("send audio: %w", err)
}

Prevention

When it happens

Trigger: Calling SendAudio when: (1) the 5-minute upload context or the dedicated uploadClient timeout expires on a large audio file; (2) MAX's /uploads endpoint returns a non-200 status (auth token invalid/expired, unsupported type); (3) the network to botapi.max.ru is down or a proxy blocks the request; (4) the audio slice is empty (uploadAttachment returns "empty attachment data").

Common situations: TTS pipeline producing large or malformed audio that exceeds upload timeouts on slow links; expired or misconfigured MAX bot access token in config.toml; corporate proxy/firewall blocking the upload CDN host; a TTS engine returning zero bytes on synthesis failure which then fails the upload with the empty-data error.

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/42baabb94a68d08d. Report an issue: GitHub.