sipeed/picoclaw · error · channels.ErrTemporary
matrix upload media: %w
Error message
matrix upload media: %w
What it means
Thrown by MatrixChannel.SendMedia when mautrix's UploadMedia (POST /_matrix/media/v3/upload) fails while uploading one resolved media part (pkg/channels/matrix/matrix.go:545). Classified as channels.ErrTemporary for manager retry with backoff; unlike the text-send path, the underlying error IS logged here (path, type, error) — read that log line for the real cause. Typical causes: file exceeding the homeserver's max_upload_size (413 M_TOO_LARGE), reverse-proxy body limits, expired auth, or network failure.
Source
Thrown at pkg/channels/matrix/matrix.go:545
}
if contentType == "" {
contentType = "application/octet-stream"
}
uploadResp, err := c.client.UploadMedia(sendCtx, mautrix.ReqUploadMedia{
Content: file,
ContentLength: fileInfo.Size(),
ContentType: contentType,
FileName: filename,
})
file.Close()
if err != nil {
logger.ErrorCF("matrix", "Failed to upload media", map[string]any{
"path": localPath,
"type": part.Type,
"error": err.Error(),
})
return nil, fmt.Errorf("matrix upload media: %w", channels.ErrTemporary)
}
msgType := matrixOutboundMsgType(part.Type, filename, contentType)
content := matrixOutboundContent(
part.Caption,
filename,
msgType,
contentType,
fileInfo.Size(),
uploadResp.ContentURI.CUString(),
)
sendResp, err := c.client.SendMessageEvent(sendCtx, roomID, event.EventMessage, content)
if err != nil {
logger.ErrorCF("matrix", "Failed to send media message", map[string]any{
"room_id": roomID.String(),
"type": msgType,
"error": err.Error(),View on GitHub (pinned to 49183d7e8d)
Solutions
- Check the logged error field for this exact upload (HTTP 413/M_TOO_LARGE vs timeout vs 401)
- Query the homeserver limit (GET /_matrix/client/v1/media/config -> max_upload_size) and enforce it before sending: compress, downscale, or refuse oversized parts
- Raise client_max_body_size / max_upload_size on your reverse proxy and homeserver if large media is required
- For transient timeouts, rely on the manager's backoff retry; for 401, re-login
Example fix
// before: uploading whatever was generated
err := ch.SendMedia(ctx, msg)
// after: enforce the homeserver upload ceiling first
const maxUpload = 50 << 20 // keep in sync with GET /_matrix/client/v1/media/config
for _, p := range msg.Parts {
if meta.Size > maxUpload {
return fmt.Errorf("part %s is %d bytes; homeserver limit %d: %w", p.Ref, meta.Size, maxUpload, channels.ErrSendFailed)
}
}
err := ch.SendMedia(ctx, msg) Defensive patterns
Strategy: validation
Validate before calling
// check homeserver upload ceiling before sending
func withinUploadLimit(ctx context.Context, hs, token string, size int64) bool {
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, hs+"/_matrix/client/v1/media/config", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := http.DefaultClient.Do(req)
if err != nil { return true } // unknown limit: attempt anyway
defer resp.Body.Close()
var cfg struct{ MaxUploadSize int64 `json:"m.upload.size"` }
if json.NewDecoder(resp.Body).Decode(&cfg) != nil { return true }
return cfg.MaxUploadSize <= 0 || size <= cfg.MaxUploadSize
} Try / catch
if _, err := matrixCh.SendMedia(ctx, msg); err != nil {
if errors.Is(err, channels.ErrTemporary) {
// check the logged 'Failed to upload media' line: 413/M_TOO_LARGE will never succeed on retry
if lastUploadErrWas413() { deadLetter(msg); return nil }
return err // network/timeout: manager backoff applies
}
return err
} Prevention
- Query max_upload_size at startup and enforce it before generating/uploading media
- Set reverse-proxy client_max_body_size at or above the homeserver limit
- Compress/downscale generated media at the source for chat delivery
- Cache upload results so retries after a send failure don't re-upload bytes
When it happens
Trigger: Uploading a video/image larger than the homeserver limit (Synapse default ~50MB, often lower behind nginx client_max_body_size); slow uplink causing timeouts; access token revoked mid-session; homeserver media API disabled or rate-limited; Content-Length mismatch after the file changed on disk mid-send.
Common situations: Bots that generate large artifacts (rendered reports, screen recordings) hitting proxy limits; self-hosted Synapse behind a restrictive reverse proxy; media uploads on constrained networks (mobile uplinks, CI); files deleted or truncated between ResolveWithMeta and open.
Related errors
- matrix send media: %w
- matrix send: %w
- API error %d: %s
- after %d retries: %w
- LLM call failed after retries: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/490986f2297298a0.
Report an issue: GitHub.