siyuan-note/siyuan · error

custom emoji file must not be empty

Error message

custom emoji file must not be empty

What it means

normalizeCustomEmojiData rejects empty downloads before any decoding. If the fetched bytes slice has length 0 there is no image to validate or store, so the operation fails with 'custom emoji file must not be empty'.

Solutions

  1. Check the URL actually serves image bytes (curl the URL and inspect the body)
  2. Pick a different source URL for the emoji
  3. Add an upstream check so empty responses are reported before normalization
  4. Inspect server-side generation if you control the emoji host

Example fix

// before
normalize(data) // data may be empty
// after
if len(data) == 0 {
    return errors.New("emoji download returned no bytes; check the URL")
}
normalize(data)
Defensive patterns

Strategy: validation

Validate before calling

const buf = await (await fetch(url)).arrayBuffer();
if (buf.byteLength === 0) throw new Error("URL returned an empty body; pick another source");

Try / catch

try {
  await registerEmoji(data);
} catch (e) {
  if (String(e).includes("must not be empty")) {
    notifyUser("Downloaded file is empty; verify the URL serves image bytes");
  }
}

Prevention

When it happens

Trigger: The downloaded response body was empty (0 bytes) with a 200 status — e.g. the server returned success with no payload, or upstream code passed a zero-length slice into normalizeCustomEmojiData.

Common situations: URL points at an empty placeholder file; server returns 200 with empty body for soft errors; redirect chain ends on an empty response.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/1f2e796c6f41c4ae. Report an issue: GitHub.

Appendix: source

Thrown at kernel/api/system.go:363

	}
	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
	switch http.DetectContentType(data) {
	case "image/png":
		ext = ".png"
	case "image/jpeg":
		ext = ".jpg"
	case "image/gif":
		ext = ".gif"
	case "image/webp":
		ext = ".webp"
	default:
		raster = false
	}
	if raster {
		config, _, decodeErr := image.DecodeConfig(bytes.NewReader(data))
		if decodeErr != nil || config.Width < 1 || config.Height < 1 || config.Width > 16384 || config.Height > 16384 ||

View on GitHub (pinned to 9f775e8a12)