{"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":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/system.go","lineNumber":356,"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":338,"sourceCodeEnd":374,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/api/system.go#L338-L374","documentation":"Reading the HTTP response body into memory failed while downloading a custom emoji. The read is performed with io.ReadAll over a LimitReader capped at maxCustomEmojiSize+1, so only genuine I/O errors (connection reset, truncated body, timeouts) reach this wrapper.","triggerScenarios":"The connection drops or the server closes the stream mid-body during downloadCustomEmojiData; TLS interruption; read timeout while streaming the body.","commonSituations":"Flaky network or unstable proxy between the kernel and the emoji host; server aborts large responses; keep-alive reuse of a stale connection.","solutions":["Retry the download; transient truncation usually clears","Check proxy/firewall stability for the remote host","Use a more reliable mirror/CDN URL for the emoji","Verify TLS/connectivity to the host (curl the URL to reproduce)"],"exampleFix":"// before\ndata, _ := io.ReadAll(body) // may fail mid-stream\n// after\ndata, err := io.ReadAll(io.LimitReader(body, maxCustomEmojiSize+1))\nif err != nil {\n    return retryDownload(url) // retry transient truncation\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await downloadEmoji(url);\n} catch (e) {\n  if (String(e).includes(\"read custom emoji response failed\")) {\n    return retryWithBackoff(() => downloadEmoji(url), 3); // transient truncation\n  }\n  throw e;\n}","preventionTips":["Retry transient network failures with backoff","Prefer stable CDN hosts over flaky origins","Keep request timeouts generous enough for the file size"],"tags":["network","io","download"],"backgroundTag":"http-request-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}