{"record":{"id":"3c8879980cc6456a","repo":"glanceapp/glance","slug":"w-could-not-read-body-w","errorCode":null,"errorMessage":"%w: could not read body: %w","messagePattern":"%w: could not read body: %w","errorType":"exception","errorClass":"errNoContent","httpStatus":null,"severity":"error","filePath":"internal/glance/widget-extension.go","lineNumber":140,"sourceCode":"\t\trequest.URL.RawQuery = options.Parameters.toQueryString()\n\t}\n\n\tfor key, value := range options.Headers {\n\t\trequest.Header.Add(key, value)\n\t}\n\n\tresponse, err := http.DefaultClient.Do(request)\n\tif err != nil {\n\t\tslog.Error(\"Failed fetching extension\", \"url\", options.URL, \"error\", err)\n\t\treturn extension{}, fmt.Errorf(\"%w: request failed: %w\", errNoContent, err)\n\t}\n\n\tdefer response.Body.Close()\n\n\tbody, err := io.ReadAll(response.Body)\n\tif err != nil {\n\t\tslog.Error(\"Failed reading response body of extension\", \"url\", options.URL, \"error\", err)\n\t\treturn extension{}, fmt.Errorf(\"%w: could not read body: %w\", errNoContent, err)\n\t}\n\n\textension := extension{}\n\n\tif response.Header.Get(extensionHeaderTitle) == \"\" {\n\t\textension.Title = \"Extension\"\n\t} else {\n\t\textension.Title = response.Header.Get(extensionHeaderTitle)\n\t}\n\n\tif response.Header.Get(extensionHeaderTitleURL) != \"\" {\n\t\textension.TitleURL = response.Header.Get(extensionHeaderTitleURL)\n\t}\n\n\tcontentType, ok := extensionStringToType[response.Header.Get(extensionHeaderContentType)]\n\n\tif !ok {\n\t\tcontentType, ok = extensionStringToType[options.FallbackContentType]","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/glanceapp/glance/blob/91324e8de762702e97b0ac5c8e36271d644d8642/internal/glance/widget-extension.go#L122-L158","documentation":"The extension response body could not be read with io.ReadAll after the request succeeded. This means the connection broke mid-transfer (unexpected EOF, reset) or the server sent a malformed chunked encoding. Wrapped with errNoContent so the widget shows an empty result.","triggerScenarios":"Server closes the connection before Content-Length bytes are sent; proxy truncates the response; network reset during a large extension payload.","commonSituations":"Extension endpoint behind a proxy with a smaller body limit than the response; server crash mid-response; mobile/flaky networks to self-hosted extensions.","solutions":["Check the extension server logs for crashes or body-size limits (nginx client_max_body_size is not it here — look at proxy buffer/timeout settings)","Retry the fetch — transient truncation often resolves itself","Reduce extension response size (fewer items) to rule out intermediary limits"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"body, err := io.ReadAll(response.Body)\nif err != nil {\n    if errors.Is(err, io.ErrUnexpectedEOF) {\n        // truncated transfer: retry once before giving up\n        response, body, err = refetchWithRetry(options.URL, 1)\n    }\n    if err != nil { return extension{}, fmt.Errorf(\"%w: could not read body: %w\", errNoContent, err) }\n}","preventionTips":["Keep extension payloads small; avoid proxies that buffer/truncate large bodies","Set sane Content-Length on extension responses so truncation is detectable"],"tags":["extension","network","truncation","io"],"backgroundTag":null,"analyzedSha":"91324e8de762702e97b0ac5c8e36271d644d8642","analyzedAt":"2026-08-15T14:12:54.279Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}