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
- Gate downloadMedia on matrixMediaURI(msgEvt) != "" (or msgEvt.URL != "" || msgEvt.File != nil) at the dispatch site
- Check the event's actual MsgType/content JSON in client debug logs to see why no media field is present
- Extend matrixMediaURI if the variant legitimately stores the URI elsewhere (e.g. File.URL for encrypted, or new fields)
- 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
- Route to media download only on MsgType m.image/m.video/m.audio/m.file AND a non-empty URI
- Keep matrixMediaURI coverage in sync when supporting new event shapes (stickers, encrypted files)
- Treat missing-URI events as skip, not error, so one bad event cannot stall sync processing
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
- invalid matrix media URL: %s
- no media store available: %w
- decrypt matrix media: %w
- matrix room ID is empty: %w
- matrix upload media: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/d4a11720d517a02b.
Report an issue: GitHub.