{"record":{"id":"dcc91d8c0cf296a7","repo":"koala73/worldmonitor","slug":"too-many-redirects","errorCode":null,"errorMessage":"Too many redirects","messagePattern":"Too many redirects","errorType":"http","errorClass":"RssProxyPolicyError","httpStatus":502,"severity":"error","filePath":"api/rss-proxy.js","lineNumber":321,"sourceCode":"      let currentUrl = parsedUrl;\n\n      for (let redirectCount = 0; redirectCount <= MAX_DIRECT_REDIRECTS; redirectCount += 1) {\n        const response = await fetchRssResponse(currentUrl.href, {\n          headers: rssFetchHeadersForHost(currentUrl.hostname),\n          redirect: 'manual',\n        }, timeout);\n\n        if (!DIRECT_REDIRECT_STATUSES.has(response.status)) {\n          return response;\n        }\n\n        const location = response.headers.get('location');\n        if (!location) {\n          return response;\n        }\n\n        if (redirectCount === MAX_DIRECT_REDIRECTS) {\n          throw new RssProxyPolicyError('Too many redirects', 502);\n        }\n\n        const redirectUrl = new URL(location, currentUrl.href);\n        assertAllowedRedirect(redirectUrl);\n        currentUrl = redirectUrl;\n      }\n    };\n\n    let response;\n    let usedRelay = false;\n\n    if (isRelayOnly) {\n      // Skip direct fetch entirely — these domains block Vercel IPs\n      response = await fetchViaRailway(feedUrl, timeout);\n      usedRelay = !!response;\n      if (!response) throw new Error(`Railway relay unavailable for relay-only domain: ${hostname}`);\n    } else {\n      try {","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/api/rss-proxy.js#L303-L339","documentation":"fetchDirect follows at most MAX_DIRECT_REDIRECTS = 3 manual hops (301/302/303/307/308). When the fourth hop is still a redirect, the proxy gives up with this RssProxyPolicyError and HTTP 502. The bound exists to keep redirect loops from burning the Edge function invocation.","triggerScenarios":"A↔B redirect loops on the origin (typically alternating apex/www rewrite rules); cookie/consent walls that redirect forever because the proxy sends no cookies; legitimate chains longer than three hops (http→https→apex→www→feed).","commonSituations":"Publisher consent-redirect walls; misconfigured origin rewrites; chains that quietly grew one hop when a new canonical domain was introduced.","solutions":["Count the hops: curl -sIL -o /dev/null -w '%{num_redirects}\\n' '<feed-url>'","Request the final destination URL directly through the proxy","Fix the origin's redirect loop — alternating apex/www rewrite rules are the usual cause","Only if chains are legitimately longer, raise MAX_DIRECT_REDIRECTS in api/rss-proxy.js and re-check the Edge timeout budget"],"exampleFix":"# before — chain longer than 3 hops\n$ curl -sIL -o /dev/null -w '%{num_redirects}\\n' 'https://old.example.com/rss'\n5                                              # proxy answers 502 Too many redirects\n\n# after — call the proxy with the resolved final URL\nGET /api/rss-proxy?url=https://final.example.com/rss","handlingStrategy":"validation","validationCode":"// Count hops before handing the URL to the proxy (limit is 3)\nasync function hopCount(url) {\n  let n = 0, current = new URL(url);\n  for (;;) {\n    const res = await fetch(current, { redirect: 'manual' });\n    const loc = res.headers.get('location');\n    if (!loc || ![301,302,303,307,308].includes(res.status)) return { hops: n, finalUrl: current.href };\n    n += 1; current = new URL(loc, current);\n    if (n > 3) return { hops: n, finalUrl: current.href };\n  }\n}","typeGuard":null,"tryCatchPattern":"try { await fetchFeedViaProxy(feedUrl); }\ncatch (err) {\n  if (/Too many redirects/.test(err.message)) {\n    const { finalUrl } = await hopCount(feedUrl);\n    return fetchFeedViaProxy(finalUrl);   // retry once with the resolved destination\n  }\n  throw err;\n}","preventionTips":["Canonicalize feed URLs to their final destination at config time","Alert when a feed's hop count grows — it often precedes a loop or a wall","Do not raise MAX_DIRECT_REDIRECTS to paper over origin redirect loops; fix the origin"],"tags":["redirect","http","rss-proxy","edge-function"],"backgroundTag":"too-many-redirects","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}