koala73/worldmonitor · warning · Error

relay returned ${resp.status}

Error message

relay returned ${resp.status}

What it means

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.

Source

Thrown at server/worldmonitor/aviation/v1/search-google-flights.ts:62

  for (const airline of airlines) {
    params.append('airlines', airline);
  }

  // Cache key uses a sorted-airlines axis so input order doesn't fragment cache hits;
  // the relay still receives airlines in the caller's order via `params`.
  const sortedAirlinesKey = [...airlines].sort().join(',');
  const cacheKey = `aviation:gf:${origin}:${destination}:${departureDate}:${req.returnDate ?? ''}:${req.cabinClass ?? ''}:${req.maxStops ?? ''}:${req.departureWindow ?? ''}:${req.sortBy ?? ''}:${passengers}:${sortedAirlinesKey}:v1`;

  try {
    const data = await cachedFetchJson<{ flights: unknown[] }>(
      cacheKey,
      CACHE_TTL,
      async () => {
        const resp = await fetch(`${relayBaseUrl}/google-flights/search?${params}`, {
          headers: getRelayHeaders(),
          signal: AbortSignal.timeout(20_000),
        });
        if (!resp.ok) throw new Error(`relay returned ${resp.status}`);
        const json = (await resp.json()) as { flights?: unknown[]; error?: string };
        if (!Array.isArray(json.flights)) throw new Error(json.error ?? 'no results');
        return { flights: json.flights };
      },
    );

    if (!data) {
      return { flights: [], degraded: true, error: 'no results' };
    }

    return {
      flights: data.flights as SearchGoogleFlightsResponse['flights'],
      degraded: false,
      error: '',
    };
  } catch (err) {
    return { flights: [], degraded: true, error: err instanceof Error ? err.message : 'search failed' };
  }

View on GitHub (pinned to 9361220cc0)

Solutions

  1. Branch on the degraded flag and error string instead of reading flights.length directly
  2. Confirm relay base URL and headers config on the calling service
  3. Correlate the status code in the error string with relay logs (401 secret vs 429 throttle vs 5xx health)
  4. Retry with backoff — cache is bypassed on failure, so the next attempt hits the relay again

Example fix

// before
const { flights } = await client.searchGoogleFlights(req);
return flights;

// after — propagate degradation instead of an empty list
const res = await client.searchGoogleFlights(req);
if (res.degraded) throw new UpstreamDegradedError(res.error);
return res.flights;
Defensive patterns

Strategy: fallback

Type guard

interface FlightsResult { flights: unknown[]; degraded: boolean; error: string }
function isDegradedFlights(r: FlightsResult): boolean {
  return r.degraded === true;
}

Try / catch

const res = await searchGoogleFlights(req);
if (res.degraded && res.error.startsWith('relay returned')) {
  return backoffAndRetry(req); // upstream status in the error string; not cached, safe to retry
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-08-21). Data as JSON: /api/errors/264d0674677500bf. Report an issue: GitHub.