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

  1. Check the logged error field for this exact upload (HTTP 413/M_TOO_LARGE vs timeout vs 401)
  2. Query the homeserver limit (GET /_matrix/client/v1/media/config -> max_upload_size) and enforce it before sending: compress, downscale, or refuse oversized parts
  3. Raise client_max_body_size / max_upload_size on your reverse proxy and homeserver if large media is required
  4. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/490986f2297298a0. Report an issue: GitHub.