{"record":{"id":"264d0674677500bf","repo":"koala73/worldmonitor","slug":"relay-returned-resp-status-264d06","errorCode":null,"errorMessage":"relay returned ${resp.status}","messagePattern":"relay returned (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"server/worldmonitor/aviation/v1/search-google-flights.ts","lineNumber":62,"sourceCode":"  for (const airline of airlines) {\n    params.append('airlines', airline);\n  }\n\n  // Cache key uses a sorted-airlines axis so input order doesn't fragment cache hits;\n  // the relay still receives airlines in the caller's order via `params`.\n  const sortedAirlinesKey = [...airlines].sort().join(',');\n  const cacheKey = `aviation:gf:${origin}:${destination}:${departureDate}:${req.returnDate ?? ''}:${req.cabinClass ?? ''}:${req.maxStops ?? ''}:${req.departureWindow ?? ''}:${req.sortBy ?? ''}:${passengers}:${sortedAirlinesKey}:v1`;\n\n  try {\n    const data = await cachedFetchJson<{ flights: unknown[] }>(\n      cacheKey,\n      CACHE_TTL,\n      async () => {\n        const resp = await fetch(`${relayBaseUrl}/google-flights/search?${params}`, {\n          headers: getRelayHeaders(),\n          signal: AbortSignal.timeout(20_000),\n        });\n        if (!resp.ok) throw new Error(`relay returned ${resp.status}`);\n        const json = (await resp.json()) as { flights?: unknown[]; error?: string };\n        if (!Array.isArray(json.flights)) throw new Error(json.error ?? 'no results');\n        return { flights: json.flights };\n      },\n    );\n\n    if (!data) {\n      return { flights: [], degraded: true, error: 'no results' };\n    }\n\n    return {\n      flights: data.flights as SearchGoogleFlightsResponse['flights'],\n      degraded: false,\n      error: '',\n    };\n  } catch (err) {\n    return { flights: [], degraded: true, error: err instanceof Error ? err.message : 'search failed' };\n  }","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/server/worldmonitor/aviation/v1/search-google-flights.ts#L44-L80","documentation":"Inside the cachedFetchJson producer of searchGoogleFlights, a non-OK status from the relay (/google-flights/search, 20s timeout) throws this Error. The handler's outer catch maps it to a degraded response {flights: [], degraded: true, error: 'relay returned <status>'}, so clients see a 200 with degraded=true; the failure is never cached, so subsequent calls retry the relay.","triggerScenarios":"Relay down/restarting (502/503); auth headers from getRelayHeaders() rejected (401 — rotated relay secret); relay throttling (429); WAF blocking the request; relay returning 500 on scraper exceptions.","commonSituations":"Relay environment variables stale after a secret rotation; relay host saturated by concurrent fare searches; relay deployed behind protective infrastructure that filters the edge's requests.","solutions":["Branch on the degraded flag and error string instead of reading flights.length directly","Confirm relay base URL and headers config on the calling service","Correlate the status code in the error string with relay logs (401 secret vs 429 throttle vs 5xx health)","Retry with backoff — cache is bypassed on failure, so the next attempt hits the relay again"],"exampleFix":"// before\nconst { flights } = await client.searchGoogleFlights(req);\nreturn flights;\n\n// after — propagate degradation instead of an empty list\nconst res = await client.searchGoogleFlights(req);\nif (res.degraded) throw new UpstreamDegradedError(res.error);\nreturn res.flights;","handlingStrategy":"fallback","validationCode":null,"typeGuard":"interface FlightsResult { flights: unknown[]; degraded: boolean; error: string }\nfunction isDegradedFlights(r: FlightsResult): boolean {\n  return r.degraded === true;\n}","tryCatchPattern":"const res = await searchGoogleFlights(req);\nif (res.degraded && res.error.startsWith('relay returned')) {\n  return backoffAndRetry(req); // upstream status in the error string; not cached, safe to retry\n}","preventionTips":["Check degraded before reading flights — an empty array means two different things here","Correlate the numeric status in the error string with relay logs when triaging","Give fare-search UIs an explicit 'data unavailable, retry' state distinct from 'no flights found'"],"tags":["aviation","relay","upstream","degraded","network"],"backgroundTag":"upstream-http-error","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}