sipeed/picoclaw · error

empty matrix media URL

Error message

empty matrix media URL

What it means

Thrown by MatrixChannel.downloadMedia when matrixMediaURI(msgEvt) returns an empty string, i.e. the inbound Matrix event content carries neither URL nor File (pkg/channels/matrix/matrix.go:1000). For encrypted media the URI lives in msgEvt.File.URL; for plain media in msgEvt.URL. The error means the event was routed into the media-download path although its content has no media reference at all.

Source

Thrown at pkg/channels/matrix/matrix.go:1000

		if err == nil {
			return ref
		}
		logger.WarnCF("matrix", "Failed to store media in MediaStore, falling back to local path", map[string]any{
			"path":  localPath,
			"error": err.Error(),
		})
	}
	return localPath
}

func (c *MatrixChannel) downloadMedia(
	ctx context.Context,
	msgEvt *event.MessageEventContent,
	mediaKind string,
) (string, error) {
	uri := matrixMediaURI(msgEvt)
	if uri == "" {
		return "", fmt.Errorf("empty matrix media URL")
	}
	parsed := uri.ParseOrIgnore()
	if parsed.IsEmpty() {
		return "", fmt.Errorf("invalid matrix media URL: %s", uri)
	}

	dlCtx := c.baseContext()
	if ctx != nil {
		dlCtx = ctx
	}
	reqCtx, cancel := context.WithTimeout(dlCtx, 20*time.Second)
	defer cancel()

	resp, err := c.client.Download(reqCtx, parsed)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Gate downloadMedia on matrixMediaURI(msgEvt) != "" (or msgEvt.URL != "" || msgEvt.File != nil) at the dispatch site
  2. Check the event's actual MsgType/content JSON in client debug logs to see why no media field is present
  3. Extend matrixMediaURI if the variant legitimately stores the URI elsewhere (e.g. File.URL for encrypted, or new fields)
  4. Skip the attachment gracefully rather than failing the whole inbound message

Example fix

// before
 if isMediaMsg(msgEvt) {
 	path, err := c.downloadMedia(ctx, msgEvt, kind)
 }

// after
 if isMediaMsg(msgEvt) && matrixMediaURI(msgEvt) != "" {
 	path, err := c.downloadMedia(ctx, msgEvt, kind)
 } else {
 	logger.DebugC("matrix", "media event without mxc URI; skipping download")
 }
Defensive patterns

Strategy: type-guard

Validate before calling

// dispatch-site guard
if msgEvt.URL == "" && (msgEvt.File == nil || msgEvt.File.URL == "") {
	return nil // event carries no media reference: skip download path
}
path, err := c.downloadMedia(ctx, msgEvt, mediaKind)

Type guard

func hasMatrixMediaURI(msgEvt *event.MessageEventContent) bool {
	if msgEvt == nil { return false }
	if msgEvt.URL != "" { return true }
	return msgEvt.File != nil && msgEvt.File.URL != ""
}

Try / catch

path, err := c.downloadMedia(ctx, msgEvt, mediaKind)
if err != nil {
	if strings.Contains(err.Error(), "empty matrix media URL") {
		return nil, nil // not actually media: drop attachment, keep processing text
	}
	return nil, err
}

Prevention

When it happens

Trigger: Dispatching a non-media event (m.text, m.notice, location, or a media type matrixMediaURI does not cover) into downloadMedia; event content where both url and file fields are absent (some stickers/voice variants or custom appservice events); msgEvt being zero-valued due to a decoding step that dropped fields.

Common situations: Extending the inbound dispatcher to new message types without updating the media-URI extractor; bridges/appservices emitting non-standard content shapes; schema/version drift where MsgType checks pass but URL/File fields moved.

Related errors


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