siyuan-note/siyuan · error

download custom emoji failed with status %d

Error message

download custom emoji failed with status %d

What it means

Returned by downloadCustomEmojiData when the remote URL passed scheme/host validation but the HTTP GET returned a status other than 200 OK. The %d is filled with response.StatusCode. This is the catch-all for 4xx/5xx from the emoji host (404 missing file, 401/403 auth, 500 server error, etc.).

Source

Thrown at kernel/api/system.go:340

	if rawURL == "" {
		return nil, fmt.Errorf("field [file] or [url] must not be empty")
	}
	return downloadCustomEmojiData(rawURL)
}

func downloadCustomEmojiData(rawURL string) ([]byte, error) {
	parsedURL, err := url.Parse(rawURL)
	if err != nil || (parsedURL.Scheme != "http" && parsedURL.Scheme != "https") || parsedURL.Host == "" {
		return nil, fmt.Errorf("invalid custom emoji URL")
	}

	response, err := util.NewCustomReqClient().R().Get(parsedURL.String())
	if err != nil {
		return nil, fmt.Errorf("download custom emoji failed: %w", err)
	}
	defer response.Body.Close()
	if response.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("download custom emoji failed with status %d", response.StatusCode)
	}
	if response.ContentLength > maxCustomEmojiSize {
		return nil, fmt.Errorf("custom emoji file is too large")
	}

	data, err := io.ReadAll(io.LimitReader(response.Body, maxCustomEmojiSize+1))
	if err != nil {
		return nil, fmt.Errorf("read custom emoji response failed: %w", err)
	}
	return data, nil
}

func normalizeCustomEmojiData(data []byte) (normalized []byte, ext string, err error) {
	if len(data) == 0 {
		return nil, "", fmt.Errorf("custom emoji file must not be empty")
	}

	raster := true

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Open the URL directly in a browser to read the actual status code and body the host returns.
  2. If 404/410, switch to a URL that still hosts the image, or upload the file directly via the file form field instead of url.
  3. If 401/403, host the emoji on a CDN/object-store that permits unauthenticated GET, or pre-download and upload as a file.
  4. If 5xx, retry after the remote service recovers; do not treat it as a SiYuan bug.

Example fix

// before
emojiURL := "https://cdn.example.com/old/icon.png"
data, err := downloadCustomEmojiData(emojiURL) // 404 -> status error

// after: host the asset on a reachable, unauthenticated URL
emojiURL := "https://my-bucket.s3.example.com/emojis/icon.png"
// or upload the file directly:
//   POST /api/system/addCustomEmoji  form field: file=@icon.png
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check the URL is reachable with a HEAD before submitting to addCustomEmoji
resp, err := http.Head(rawURL)
if err != nil { return fmt.Errorf("emoji URL unreachable: %w", err) }
if resp.StatusCode != http.StatusOK { return fmt.Errorf("emoji host returned %d", resp.StatusCode) }
if ct := resp.Header.Get("Content-Type"); !strings.HasPrefix(ct, "image/") {
    return fmt.Errorf("emoji URL is not an image: %s", ct)
}

Try / catch

data, err := downloadCustomEmojiData(rawURL)
if err != nil {
    if strings.Contains(err.Error(), "failed with status") {
        // remote-side error; surface the status to the user, do not retry 4xx
        return fmt.Errorf("emoji source refused: %s", err)
    }
    return err
}

Prevention

When it happens

Trigger: POST /api/system/addCustomEmoji with the url form field (no file upload) where the resolved URL is reachable but returns non-200: a typo'd path on a CDN (404), a hotlink-protected host (403), or a temporarily down server (5xx).

Common situations: Pasting a stale image link whose host deleted the asset; hotlink protection on the source site; the URL points to an HTML login page (200 with HTML would instead fail at content-type detection, but a 302-to-login followed by 401 lands here); corporate proxy returning 502.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/7f9697b0c2a85c42. Report an issue: GitHub.