{"record":{"id":"2a575580a06bd298","repo":"koala73/worldmonitor","slug":"no-results-2a5755","errorCode":null,"errorMessage":"no results","messagePattern":"no results","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"server/worldmonitor/aviation/v1/search-google-flights.ts","lineNumber":64,"sourceCode":"  }\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  }\n}\n","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/server/worldmonitor/aviation/v1/search-google-flights.ts#L46-L82","documentation":"Thrown inside the searchGoogleFlights cache producer when the relay returned 2xx but json.flights is not an array — the body's error field is re-thrown if present, otherwise 'no results'. The outer catch converts it to {flights: [], degraded: true, error: 'no results'}; the identical string is also returned (without throwing) when cachedFetchJson yields null.","triggerScenarios":"Relay 200 with {error: ...} because the Google Flights scrape failed (anti-bot, captcha); relay schema drift where flights is missing or an object; legitimately unavailable route/date combination reported via error.","commonSituations":"Scraper blocked by upstream anti-bot measures; cabin class / passenger combinations the relay cannot serve; relay response-shape change after an update.","solutions":["Sanity-check the query (valid IATA pair, future departure date, supported cabin/stops values)","Treat degraded=true + 'no results' as retryable upstream unavailability, not a definitive empty set","Check relay logs if valid queries consistently degrade — scraper health is the usual culprit"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// cheap client-side sanity before the call\nif (!origin || !destination || !departureDate) return skip('missing flight search params');","typeGuard":"function isNoResultsFlights(r: { degraded: boolean; error: string }): boolean {\n  return r.degraded && r.error === 'no results';\n}","tryCatchPattern":"const res = await searchGoogleFlights(req);\nif (isNoResultsFlights(res)) return offerRetry(); // scrape may have failed — not a definitive answer","preventionTips":["Distinguish degraded 'no results' from a genuine empty schedule in UI copy","Keep last-known-good flight data in client state to ride out relay scrape failures","Monitor the ratio of degraded responses per route to detect scraper blocking early"],"tags":["aviation","relay","empty-results","degraded"],"backgroundTag":"empty-search-results","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"}