{"record":{"id":"e847596f12809731","repo":"koala73/worldmonitor","slug":"no-results","errorCode":null,"errorMessage":"no results","messagePattern":"no results","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"server/worldmonitor/aviation/v1/search-google-dates.ts","lineNumber":64,"sourceCode":"  });\n  for (const airline of airlines) {\n    params.append('airlines', airline);\n  }\n\n  const cacheKey = `aviation:gf-dates:${origin}:${destination}:${startDate}:${endDate}:${params.toString()}:v1`;\n\n  try {\n    const data = await cachedFetchJson<{ dates: unknown[]; partial?: boolean }>(\n      cacheKey,\n      CACHE_TTL,\n      async () => {\n        const resp = await fetch(`${relayBaseUrl}/google-flights/search-dates?${params}`, {\n          headers: getRelayHeaders(),\n          signal: AbortSignal.timeout(30_000),\n        });\n        if (!resp.ok) throw new Error(`relay returned ${resp.status}`);\n        const json = (await resp.json()) as { dates?: unknown[]; partial?: boolean; error?: string };\n        if (!Array.isArray(json.dates)) throw new Error(json.error ?? 'no results');\n        return { dates: json.dates, partial: json.partial };\n      },\n    );\n\n    if (!data) {\n      return { dates: [], degraded: true, error: 'no results' };\n    }\n\n    return {\n      dates: data.dates as SearchGoogleDatesResponse['dates'],\n      degraded: data.partial === true,\n      error: data.partial === true ? 'partial results: one or more date chunks failed' : '',\n    };\n  } catch (err) {\n    return { dates: [], 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-dates.ts#L46-L82","documentation":"Thrown inside the searchGoogleDates cache producer when the relay answered 2xx but the JSON body has no dates array — either the body carried an error field (json.error is re-thrown as the message) or the shape was unexpected ('no results' is the fallback message). The outer catch turns it into the degraded response {dates: [], degraded: true, error: 'no results'}; the same string also appears on the cachedFetchJson null path without any throw.","triggerScenarios":"Relay returns 200 with {error: 'scrape failed'} after the Google Flights scraper got blocked; relay returns 200 with an unexpected body shape (dates missing or not an array); genuine empty route/date-range where the relay reports it via error instead of an empty array.","commonSituations":"Google Flights anti-bot countermeasures causing the relay's scraper to fail; requesting an invalid or unserved origin/destination/date combination; relay version returning a changed response schema.","solutions":["Verify the request parameters (IATA codes, start/end dates within a servable range)","Check the degraded flag and surface a retry-later message rather than 'no results found'","If it persists for valid routes, inspect the relay scraper logs — a 200-with-error usually means the upstream scrape failed","Retry later; anti-bot failures are often transient and nothing is cached on failure"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// validate the route/date inputs before spending a relay round-trip\nconst IATA = /^[A-Z]{3}$/;\nif (!IATA.test(origin) || !IATA.test(destination) || !/\\d{4}-\\d{2}-\\d{2}/.test(startDate)) {\n  return clientError('invalid origin/destination/dates');\n}","typeGuard":"function isNoResultsDegraded(r: { degraded: boolean; error: string }): boolean {\n  return r.degraded && r.error === 'no results';\n}","tryCatchPattern":"const res = await searchGoogleDates(req);\nif (isNoResultsDegraded(res)) {\n  // ambiguous between 'truly empty' and 'relay scrape failed' — surface retry, not a definitive empty\n}","preventionTips":["Treat 'no results' with degraded=true as upstream unavailability, never as a confirmed empty result","Cache positive answers client-side so transient relay scrape failures do not blank your UI","Watch the degraded rate — a spike usually means the relay's Google Flights scraper is being blocked"],"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-14T00:17:10.932Z"}