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
- 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.
- Verify the MAX bot token in config.toml is valid — an auth failure surfaces here as a non-200 from /uploads.
- Check network/proxy access to the MAX API host from the machine running cc-connect; retry transient failures.
- Guard the input: check len(audio) > 0 and that the TTS step succeeded before calling SendAudio.
- 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
- Validate audio bytes are non-empty before calling SendAudio.
- Keep the bot access token valid and rotated per config; auth failures surface at upload time.
- Allow generous context deadlines (platform allows up to 5 min) for large audio files.
- Verify outbound network access to the MAX API/upload hosts at deployment time (a doctor/health check).
- Retry transient network failures with backoff; uploads are safe to re-attempt (no message sent until postMessage succeeds).
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
- max: edit message: %w
- listen for Agy permission hooks: %w
- antigravity permission bridge: %w
- request usage endpoint: %w
- listen unix socket: %w
AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06).
Data as JSON: /api/errors/42baabb94a68d08d.
Report an issue: GitHub.