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

  1. Log the raw URI from the message and inspect the originating event JSON to see who wrote a non-mxc value
  2. If http(s) URLs are expected from a bridge, fetch them out-of-band with a normal HTTP client instead of client.Download
  3. Skip the attachment with a user-visible notice rather than failing message processing
  4. 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

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


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