{"record":{"id":"91c44f011ca1101f","repo":"glanceapp/glance","slug":"unexpected-status-code-d-from-s-response-s","errorCode":null,"errorMessage":"unexpected status code %d from %s, response: %s","messagePattern":"unexpected status code (.+?) from (.+?), response: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/glance/widget-utils.go","lineNumber":79,"sourceCode":"\nfunc decodeJsonFromRequest[T any](client requestDoer, request *http.Request) (T, error) {\n\tvar result T\n\n\tresponse, err := client.Do(request)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\tdefer response.Body.Close()\n\n\tbody, err := io.ReadAll(response.Body)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\n\tif response.StatusCode != http.StatusOK {\n\t\ttruncatedBody, _ := limitStringLength(string(body), 256)\n\n\t\treturn result, fmt.Errorf(\n\t\t\t\"unexpected status code %d from %s, response: %s\",\n\t\t\tresponse.StatusCode,\n\t\t\trequest.URL,\n\t\t\ttruncatedBody,\n\t\t)\n\t}\n\n\terr = json.Unmarshal(body, &result)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\n\treturn result, nil\n}\n\nfunc decodeJsonFromRequestTask[T any](client requestDoer) func(*http.Request) (T, error) {\n\treturn func(request *http.Request) (T, error) {\n\t\treturn decodeJsonFromRequest[T](client, request)","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/glanceapp/glance/blob/91324e8de762702e97b0ac5c8e36271d644d8642/internal/glance/widget-utils.go#L61-L97","documentation":"Generic helper (decodeJsonFromRequest) that performs a GET and decodes JSON: the response status was not 200, so it reports the code, the request URL, and up to 256 bytes of the body. It is the primary network/API error surfaced by many widgets (markets, weather geocoding, Twitch alternatives, etc.).","triggerScenarios":"Any upstream JSON API returning 4xx/5xx: 401/403 for missing or bad API keys, 429 for rate limits, 404 for a wrong feed URL, 5xx outages, or a captive portal returning 200-less HTML.","commonSituations":"Expired or absent API token for a widget that requires one; aggressive refresh (low cache settings) hitting rate limits; upstream maintenance; DNS/proxy returning error pages.","solutions":["Read the truncated response body in the message — it usually names the exact cause (invalid key, quota exceeded, etc.)","Fix the credential/site config for the widget that failed (check its docs for token fields)","Raise the widget's cache duration to reduce request frequency if the status is 429","Retry later or check the upstream service's status page for 5xx"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Verify the endpoint is reachable and returns 200 before the widget calls it\nresp, err := http.Get(apiURL) // in a preflight or healthcheck\nif err == nil {\n    defer resp.Body.Close()\n    _ = resp.StatusCode // act on non-200 before relying on the API\n}","typeGuard":"func isUnexpectedStatus(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"unexpected status code\")\n}","tryCatchPattern":"data, err := decodeJsonFromRequest[ T ](client, req)\nif err != nil {\n    if isUnexpectedStatus(err) {\n        // parse the code from the message; back off on 429, surface 401/403 as config problems\n        slog.Warn(\"upstream API error\", \"err\", err)\n        return retryWithBackoff(...)  // for 429/5xx only\n    }\n    return err\n}","preventionTips":["Configure required API keys for widgets before enabling them","Set per-widget cache times so refresh frequency stays under rate limits","Monitor the truncated response body — upstream error messages identify key/quota issues immediately"],"tags":["http","network","json","api","rate-limit"],"backgroundTag":null,"analyzedSha":"91324e8de762702e97b0ac5c8e36271d644d8642","analyzedAt":"2026-08-15T14:12:54.279Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}