{"record":{"id":"bcdaef7f5bdc493b","repo":"koala73/worldmonitor","slug":"redis-unavailable","errorCode":null,"errorMessage":"Redis unavailable","messagePattern":"Redis unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"server/_shared/redis.ts","lineNumber":1213,"sourceCode":"  } catch (error) {\n    logCacheReadError(args[0], error);\n    return [];\n  }\n}\n\nexport async function getHashFieldsBatch(...args: Parameters<typeof getHashFieldsBatchStrict>): Promise<Map<string, string>> {\n  try {\n    return await getHashFieldsBatchStrict(...args);\n  } catch (error) {\n    logCacheReadError(args[0], error);\n    return new Map();\n  }\n}\n\nexport async function geoSearchByBoxStrict(key: string, lon: number, lat: number, widthKm: number, heightKm: number, count: number, raw = false): Promise<string[]> {\n  const url = process.env.UPSTASH_REDIS_REST_URL;\n  const token = process.env.UPSTASH_REDIS_REST_TOKEN;\n  if (!url || !token) throw new Error('Redis unavailable');\n  const finalKey = raw ? key : prefixKey(key);\n  const pipeline = [['GEOSEARCH', finalKey, 'FROMLONLAT', String(lon), String(lat), 'BYBOX', String(widthKm), String(heightKm), 'km', 'ASC', 'COUNT', String(count)]];\n  const resp = await fetch(`${url}/pipeline`, {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${token}`,\n      'Content-Type': 'application/json',\n      'User-Agent': 'worldmonitor-server/1.0 (redis)',\n    },\n    body: JSON.stringify(pipeline),\n    signal: AbortSignal.timeout(REDIS_PIPELINE_TIMEOUT_MS),\n  });\n  if (!resp.ok) throw new Error(`Redis HTTP ${resp.status}`);\n  const data = (await resp.json()) as Array<{ result?: string[]; error?: string }>;\n  if (data[0]?.error || !Array.isArray(data[0]?.result) || !data[0].result.every(value => typeof value === 'string')) throw new Error('Invalid GEOSEARCH result');\n  return data[0].result;\n}\n","sourceCodeStart":1195,"sourceCodeEnd":1231,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/server/_shared/redis.ts#L1195-L1231","documentation":"geoSearchByBoxStrict is the strict variant of the Upstash Redis GEOSEARCH helper in server/_shared/redis.ts. It reads UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN from the environment and throws immediately when either is unset, because a strict geo query cannot proceed without REST credentials.","triggerScenarios":"Calling geoSearchByBoxStrict (directly or via a maritime/geospatial endpoint) in an environment where process.env.UPSTASH_REDIS_REST_URL or UPSTASH_REDIS_REST_TOKEN is missing or empty — e.g. local dev without a .env loaded, a preview deployment without the secrets configured, or the worker process not going through loadEnvFile().","commonSituations":"Local development without Upstash credentials in .env; Vercel/Railway environment variables not set for the environment; env vars renamed or mistyped; tests running without secret injection.","solutions":["Set UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN in the environment (or .env loaded via loadEnvFile()).","Verify the variable names match exactly (case-sensitive) and contain non-empty values.","Restart/redeploy the process after adding the env vars so they are picked up.","Use the non-strict variant or return an empty/degraded result if the caller should tolerate a missing Redis configuration."],"exampleFix":"// before\nconst result = await geoSearchByBoxStrict('vessels:geo', lon, lat, 50, 50, 100);\n// after\nif (!process.env.UPSTASH_REDIS_REST_URL || !process.env.UPSTASH_REDIS_REST_TOKEN) {\n  return []; // degraded mode\n}\nconst result = await geoSearchByBoxStrict('vessels:geo', lon, lat, 50, 50, 100);","handlingStrategy":"fallback","validationCode":"export function hasRedisConfig(): boolean {\n  return Boolean(process.env.UPSTASH_REDIS_REST_URL && process.env.UPSTASH_REDIS_REST_TOKEN);\n}","typeGuard":"const isRedisConfigured = (): boolean =>\n  typeof process.env.UPSTASH_REDIS_REST_URL === 'string' &&\n  process.env.UPSTASH_REDIS_REST_URL.length > 0 &&\n  typeof process.env.UPSTASH_REDIS_REST_TOKEN === 'string' &&\n  process.env.UPSTASH_REDIS_REST_TOKEN.length > 0;","tryCatchPattern":"let results: string[];\ntry {\n  results = await geoSearchByBoxStrict(key, lon, lat, w, h, count);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Redis unavailable') {\n    results = []; // degraded mode without Redis\n  } else {\n    throw err;\n  }\n}","preventionTips":["Load credentials via loadEnvFile() in every entrypoint; never read $HOME or hardcode paths.","Add UPSTASH_REDIS_REST_URL/TOKEN to .env.example and CI secret setup.","Assert required env vars at process startup with a fail-fast check.","Use the strict variant only when the feature genuinely cannot degrade; otherwise prefer non-strict helpers."],"tags":["redis","environment","configuration","upstash"],"backgroundTag":"missing-env-var","analyzedSha":"e586b8b4b80f595aa7ece295eec10d76f2921240","analyzedAt":"2026-09-22T01:50:49.965Z","contentChangedAt":"2026-09-22T01:50:49.965Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}