{"record":{"id":"9529468de01c19d5","repo":"glanceapp/glance","slug":"unexpected-status-code-d-from-s","errorCode":null,"errorMessage":"unexpected status code %d from %s","messagePattern":"unexpected status code (.+?) from (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/glance/widget-rss.go","lineNumber":227,"sourceCode":"\t}\n\twidget.cachedFeedsMutex.Unlock()\n\n\tfor key, value := range request.Headers {\n\t\treq.Header.Set(key, value)\n\t}\n\n\tresp, err := defaultHTTPClient.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode == http.StatusNotModified && isCached {\n\t\treturn cache.items, nil\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"unexpected status code %d from %s\", resp.StatusCode, request.URL)\n\t}\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfeed, err := feedParser.ParseString(string(body))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif request.Limit > 0 && len(feed.Items) > request.Limit {\n\t\tfeed.Items = feed.Items[:request.Limit]\n\t}\n\n\titems := make(rssFeedItemList, 0, len(feed.Items))\n","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/glanceapp/glance/blob/91324e8de762702e97b0ac5c8e36271d644d8642/internal/glance/widget-rss.go#L209-L245","documentation":"Returned by rssWidget.fetchItemsFromFeedTask (internal/glance/widget-rss.go:227) when a feed URL responds with a status other than 200 (or 304 when a cached copy exists). The request already carries a Glance user-agent; non-200 means the server refused or failed the fetch: 403 anti-bot, 404 moved feed, 410 gone, 429 rate-limited, or 5xx outage.","triggerScenarios":"GET {feed-url} returns 403 (Cloudflare/WAF blocking the Glance UA), 404/410 (feed path moved), 429 (refreshing too often), 301→non-200 chain where the redirect target errors, or 5xx from the origin. 304 is tolerated only when isCached is true; a 304 without cache still errors.","commonSituations":"Popular blogs behind Cloudflare blocking non-browser agents; feeds moved to /rss or /feed without redirect; widget refresh interval set aggressively low triggering 429s; origin outages.","solutions":["Confirm the URL with curl -A 'Glance' and follow redirects to find the current feed address","If the site blocks bots, use its RSS-specific domain or a feed proxy/burner URL that permits fetchers","Increase the page cache-time so the feed is fetched less often (fixes 429)","Remove feeds that are permanently gone (410)"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-check a feed URL's health before adding it to the widget\nresp, err := http.Get(feedURL)\nif err != nil || (resp.StatusCode != 200 && resp.StatusCode != 304) {\n    // do not add / fix the URL\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"unexpected status code\") {\n    // 403 => bot blocking; 404/410 => moved/gone; 429 => back off; 5xx => retry later\n}","preventionTips":["curl feeds with the Glance user-agent before adding them","Prefer feed endpoints that permit fetchers","Keep refresh intervals moderate to avoid 429s"],"tags":["rss","http-status","feeds","anti-bot"],"backgroundTag":null,"analyzedSha":"91324e8de762702e97b0ac5c8e36271d644d8642","analyzedAt":"2026-08-15T14:12:54.279Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}