siyuan-note/siyuan · error
read custom emoji response failed: %w
Error message
read custom emoji response failed: %w
What it means
Wraps an io.ReadAll failure while reading the (LimitReader-capped, maxCustomEmojiSize+1 bytes) response body of a downloaded emoji URL. The %w preserves the underlying network/IO cause (connection reset, TLS handshake drop, read deadline, EOF mid-chunk).
Source
Thrown at kernel/api/system.go:348
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
switch http.DetectContentType(data) {
case "image/png":
ext = ".png"
case "image/jpeg":
ext = ".jpg"
case "image/gif":
ext = ".gif"
case "image/webp":View on GitHub (pinned to 251596fc0d)
Solutions
- Retry the request once or twice; transient mid-stream failures often succeed on retry thanks to NewCustomReqClient's retry semantics.
- Download the file locally with curl/wget to confirm the source is stable; if it also fails locally, the host is the problem.
- If the host is unreliable, download manually and upload via the file form field instead of url.
- Inspect the wrapped error (errors.Unwrap) to distinguish TLS errors from plain EOF.
Example fix
// before
data, err := downloadCustomEmojiData(rawURL)
if err != nil { /* generic failure, no retry */ }
// after: surface the wrapped cause and retry transient IO errors
data, err := downloadCustomEmojiData(rawURL)
if err != nil {
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
// retry once
data, err = downloadCustomEmojiData(rawURL)
}
} Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight: confirm the URL responds quickly with a HEAD
resp, err := util.NewCustomReqClient().R().Head(rawURL)
if err != nil || resp.StatusCode != http.StatusOK {
return fmt.Errorf("emoji source not reliably reachable")
} Try / catch
var data []byte
var err error
for attempt := 0; attempt < 2; attempt++ {
data, err = downloadCustomEmojiData(rawURL)
if err == nil { break }
var netErr net.Error
if errors.As(err, &netErr) && (netErr.Timeout() || errors.Is(err, io.ErrUnexpectedEOF)) {
continue // retry transient mid-stream IO failure
}
break // non-transient, stop
} Prevention
- Use a stable network path when downloading emojis.
- If the source is flaky, download locally and upload as a file.
- Inspect errors.Unwrap to distinguish retryable IO errors from permanent ones.
When it happens
Trigger: POST /api/system/addCustomEmoji with url pointing at a host that starts a 200 response then drops the connection mid-transfer, or a flaky mobile network causing the read to abort.
Common situations: Unstable mobile/hotspot connection during download; source server closing keep-alive early; intermediary proxy truncating large responses; TLS renegotiation failure after headers.
Related errors
- download custom emoji failed with status %d
- invalid custom emoji URL
- download custom emoji failed: %w
- custom emoji file is too large
- custom emoji file must not be empty
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/98f2019529b7ae5a.
Report an issue: GitHub.