sipeed/picoclaw · error
invalid matrix media URL: %s
Error message
invalid matrix media URL: %s
What it means
Thrown by MatrixChannel.downloadMedia when the media URI string exists but id.ContentURI.ParseOrIgnore yields an empty ContentURI — the value is not a valid mxc://server/mediaId reference (pkg/channels/matrix/matrix.go:1004). The offending raw URI is included in the message. Matrix content repositories address media exclusively by mxc:// URIs; anything else (http links, truncated strings, custom schemes) cannot be downloaded via client.Download.
Source
Thrown at pkg/channels/matrix/matrix.go:1004
"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()
reader := resp.Body
readerClose := func() error { return nil }
View on GitHub (pinned to 49183d7e8d)
Solutions
- Log the raw URI from the message and inspect the originating event JSON to see who wrote a non-mxc value
- If http(s) URLs are expected from a bridge, fetch them out-of-band with a normal HTTP client instead of client.Download
- Skip the attachment with a user-visible notice rather than failing message processing
- For federated media, ensure the server part of the mxc URI resolves (correct server name)
Example fix
// before
resp, err := c.client.Download(reqCtx, parsed) // parsed empty, fails oddly
// after: gate on a strict mxc check before download
if uri := matrixMediaURI(msgEvt); uri != "" {
parsed := uri.ParseOrIgnore()
if parsed.IsEmpty() {
logger.WarnCF("matrix", "skipping non-mxc media URI", map[string]any{"uri": string(uri)})
return "", nil // or a typed skip error
}
} Defensive patterns
Strategy: type-guard
Validate before calling
// strict mxc check before download
var mxcPattern = regexp.MustCompile(`^mxc://[^/]+/[^/]+$`)
uri := matrixMediaURI(msgEvt)
if !mxcPattern.MatchString(string(uri)) {
return nil, &SkipAttachmentError{Reason: "non-mxc media URI: " + string(uri)}
} Type guard
func isUsableMxcURI(uri id.ContentURIString) bool {
parsed := uri.ParseOrIgnore()
return !parsed.IsEmpty() && parsed.Homeserver != "" && parsed.FileID != ""
} Try / catch
path, err := c.downloadMedia(ctx, msgEvt, mediaKind)
if err != nil {
if strings.Contains(err.Error(), "invalid matrix media URL") {
log.Warn("skipping malformed media URI: ", err)
return nil, nil // untrusted input: skip, never retry
}
return nil, err
} Prevention
- Validate mxc URIs at the trust boundary — inbound event content is untrusted
- Bridges emitting https URLs need a separate fetch path, not client.Download
- Log and skip malformed URIs so adversarial events cannot DoS the media pipeline
When it happens
Trigger: A homeserver or bridge populating the url field with an https:// link instead of mxc:// (some bridges do this for avatars/files); a truncated or hand-crafted event with garbage in url; content rewritten by a non-compliant client; mxc URIs with empty media ID or missing server part.
Common situations: Bridging from Slack/Discord/Telegram via software that inlines external URLs; events from defederated/custom servers with unusual URI formats; fuzzed or adversarial event content reaching the bot.
Related errors
- empty matrix media URL
- 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/22462af1628abc88.
Report an issue: GitHub.