{"record":{"id":"9cf9daaea48fdd42","repo":"koala73/worldmonitor","slug":"railway-relay-unavailable-for-relay-only-domain","errorCode":null,"errorMessage":"Railway relay unavailable for relay-only domain: ${hostname}","messagePattern":"Railway relay unavailable for relay-only domain: (.+?)","errorType":"http","errorClass":"Error","httpStatus":502,"severity":"error","filePath":"api/rss-proxy.js","lineNumber":185,"sourceCode":"\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 {\n        response = await fetchDirect();\n      } catch (directError) {\n        if (directError instanceof RssProxyPolicyError) throw directError;\n        // A throwing relay leg here must not replace directError — a null or\n        // non-ok relay response already falls through to it below, so a thrown\n        // relay error should too, rather than becoming the reported failure.\n        let relayResponse = null;\n        try {\n          relayResponse = await fetchViaRailway(feedUrl, timeout);\n        } catch (relayError) {\n          console.error('RSS proxy relay fallback error:', feedUrl, relayError instanceof Error ? relayError.message : String(relayError));\n        }\n        response = relayResponse;\n        usedRelay = !!response;\n        if (!response) throw directError;\n      }","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/api/rss-proxy.js#L167-L203","documentation":"Thrown by the RSS proxy when a relay-only domain (listed in RELAY_ONLY_DOMAINS — domains known to block Vercel edge IPs) is requested but fetchViaRailway() returned a falsy value. fetchViaRailway returns null when getRelayBaseUrl() is empty (the Railway relay URL environment variable is not configured), meaning the relay path is entirely unavailable. For relay-only domains, direct fetch is skipped, so this is a total failure — there is no fallback path.","triggerScenarios":"Requesting a feed from a relay-only domain (e.g. rss.cnn.com, www.scmp.com, kyivindependent.com) when the Railway relay service URL is not set in the edge function's environment — getRelayBaseUrl() returns null, fetchViaRailway returns null, and since direct fetch is skipped for relay-only domains, there is no other path. This is a configuration/infrastructure outage, not a transient network issue.","commonSituations":"The RELAY_BASE_URL (or equivalent) env var is missing from the Vercel edge function environment after a deploy; the Railway relay service is down and its URL was removed/emptied; a new relay-only domain was added to the set but the relay service was not deployed; env var naming drift between local and deployed environments.","solutions":["Verify the Railway relay URL environment variable is set in the Vercel edge function configuration (check getRelayBaseUrl and the env var it reads).","Confirm the Railway relay service is running and responding at the configured URL.","If the relay is temporarily down, the feed cannot be fetched — this is expected for relay-only domains. Wait for relay recovery.","If this is a new relay-only domain addition, ensure the Railway relay service can actually reach it before adding to RELAY_ONLY_DOMAINS."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// Check relay configuration before requesting a relay-only feed\nconst RELAY_ONLY_DOMAINS = ['rss.cnn.com','www.defensenews.com','layoffs.fyi','news.un.org','www.cisa.gov','www.iaea.org','www.who.int','www.crisisgroup.org','english.alarabiya.net','www.timesofisrael.com','www.scmp.com','kyivindependent.com','www.themoscowtimes.com','feeds.24.com','feeds.capi24.com','islandtimes.org','www.atlanticcouncil.com'];\nconst isRelayOnly = RELAY_ONLY_DOMAINS.includes(new URL(feedUrl).hostname);\nif (isRelayOnly) {\n  const health = await fetch('/api/health').then(r => r.json());\n  if (!health.relayReachable) {\n    throw new Error('Railway relay is down; relay-only feeds unavailable until recovery');\n  }\n}","typeGuard":"function isRailwayRelayUnavailableError(e: unknown): boolean {\n  return e instanceof Error && e.message.startsWith('Railway relay unavailable for relay-only domain');\n}","tryCatchPattern":"try {\n  const feed = await fetch('/api/rss-proxy?url=' + encodeURIComponent(feedUrl));\n} catch (e) {\n  if (isRailwayRelayUnavailableError(e)) {\n    // No fallback for relay-only domains — the relay is the only path\n    // Retry after confirming relay recovery, or skip this feed\n    logRelayOutage(e.message);\n  } else throw e;\n}","preventionTips":["Monitor the Railway relay service health independently — relay-only domains have no direct-fetch fallback.","Ensure the relay URL env var is set in all edge function environments (preview, production).","Do not add a domain to RELAY_ONLY_DOMAINS unless the Railway relay can actually reach it."],"tags":["rss-proxy","railway","relay","config","edge-function"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}