{"record":{"id":"98f2019529b7ae5a","repo":"siyuan-note/siyuan","slug":"read-custom-emoji-response-failed-w","errorCode":null,"errorMessage":"read custom emoji response failed: %w","messagePattern":"read custom emoji response failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/system.go","lineNumber":348,"sourceCode":"\tif err != nil || (parsedURL.Scheme != \"http\" && parsedURL.Scheme != \"https\") || parsedURL.Host == \"\" {\n\t\treturn nil, fmt.Errorf(\"invalid custom emoji URL\")\n\t}\n\n\tresponse, err := util.NewCustomReqClient().R().Get(parsedURL.String())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"download custom emoji failed: %w\", err)\n\t}\n\tdefer response.Body.Close()\n\tif response.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"download custom emoji failed with status %d\", response.StatusCode)\n\t}\n\tif response.ContentLength > maxCustomEmojiSize {\n\t\treturn nil, fmt.Errorf(\"custom emoji file is too large\")\n\t}\n\n\tdata, err := io.ReadAll(io.LimitReader(response.Body, maxCustomEmojiSize+1))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read custom emoji response failed: %w\", err)\n\t}\n\treturn data, nil\n}\n\nfunc normalizeCustomEmojiData(data []byte) (normalized []byte, ext string, err error) {\n\tif len(data) == 0 {\n\t\treturn nil, \"\", fmt.Errorf(\"custom emoji file must not be empty\")\n\t}\n\n\traster := true\n\tswitch http.DetectContentType(data) {\n\tcase \"image/png\":\n\t\text = \".png\"\n\tcase \"image/jpeg\":\n\t\text = \".jpg\"\n\tcase \"image/gif\":\n\t\text = \".gif\"\n\tcase \"image/webp\":","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/api/system.go#L330-L366","documentation":"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).","triggerScenarios":"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.","commonSituations":"Unstable mobile/hotspot connection during download; source server closing keep-alive early; intermediary proxy truncating large responses; TLS renegotiation failure after headers.","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."],"exampleFix":"// before\ndata, err := downloadCustomEmojiData(rawURL)\nif err != nil { /* generic failure, no retry */ }\n\n// after: surface the wrapped cause and retry transient IO errors\ndata, err := downloadCustomEmojiData(rawURL)\nif err != nil {\n    var netErr net.Error\n    if errors.As(err, &netErr) && netErr.Timeout() {\n        // retry once\n        data, err = downloadCustomEmojiData(rawURL)\n    }\n}","handlingStrategy":"retry","validationCode":"// Pre-flight: confirm the URL responds quickly with a HEAD\nresp, err := util.NewCustomReqClient().R().Head(rawURL)\nif err != nil || resp.StatusCode != http.StatusOK {\n    return fmt.Errorf(\"emoji source not reliably reachable\")\n}","typeGuard":null,"tryCatchPattern":"var data []byte\nvar err error\nfor attempt := 0; attempt < 2; attempt++ {\n    data, err = downloadCustomEmojiData(rawURL)\n    if err == nil { break }\n    var netErr net.Error\n    if errors.As(err, &netErr) && (netErr.Timeout() || errors.Is(err, io.ErrUnexpectedEOF)) {\n        continue // retry transient mid-stream IO failure\n    }\n    break // non-transient, stop\n}","preventionTips":["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."],"tags":["network","io","emoji","retry","api"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}