{"record":{"id":"e57c6ba7a8546a39","repo":"koala73/worldmonitor","slug":"invalid-geosearch-result","errorCode":null,"errorMessage":"Invalid GEOSEARCH result","messagePattern":"Invalid GEOSEARCH result","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/_shared/redis.ts","lineNumber":1228,"sourceCode":"export 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\nexport async function getHashFieldsBatchStrict(\n  key: string,\n  fields: string[],\n  raw = false,\n  timeoutMs?: number,\n): Promise<Map<string, string>> {\n  const result = new Map<string, string>();\n  if (fields.length === 0) return result;\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 = [['HMGET', finalKey, ...fields]];\n  const resp = await fetch(`${url}/pipeline`, {\n    method: 'POST',","sourceCodeStart":1210,"sourceCodeEnd":1246,"githubUrl":"https://github.com/koala73/worldmonitor/blob/e586b8b4b80f595aa7ece295eec10d76f2921240/server/_shared/redis.ts#L1210-L1246","documentation":"After sending the GEOSEARCH pipeline command to the Upstash REST API, the helper validates the response shape: it must be an array whose first entry has no error field and a result that is an array of strings. If the response is malformed, contains a per-command error, or has an unexpected element type, the helper throws instead of returning untrusted data as search results.","triggerScenarios":"Upstash returns 200 but the pipeline body indicates an error (e.g. WRONGTYPE because the key holds a non-geo SET, or NOGROUP/key-type errors); the JSON parses into an unexpected shape (null entry, result missing, numeric members); a proxy/gateway intercepts and returns HTML or a different JSON schema.","commonSituations":"GEOSEARCH executed against a key that was populated with ZADD or SET instead of GEOADD; Upstash incident or API change altering the pipeline response envelope; a CDN/error page returned with a 200 status; mistyped key colliding with a different data structure after prefixKey().","solutions":["Log data[0].error to see the underlying Redis error and fix the key's type (delete and repopulate with GEOADD).","Confirm the key holds a geospatial set (TYPE <key> == zset populated via GEOADD) and that the name matches after prefixKey().","Retry on transient upstream issues; check Upstash status/incidents if shapes changed suddenly.","Harden the caller to catch this error and return an empty result set for degraded operation."],"exampleFix":"// before\nconst members = await geoSearchByBoxStrict(key, lon, lat, w, h, 10);\n// after\nlet members: string[] = [];\ntry {\n  members = await geoSearchByBoxStrict(key, lon, lat, w, h, 10);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Invalid GEOSEARCH result') {\n    console.error('geoSearchByBoxStrict failed for key', key, err);\n  } else throw err;\n}","handlingStrategy":"try-catch","validationCode":"// pre-check the key type before GEOSEARCH (strict helper assumes a geo set)\nconst keyType = await redisType(finalKey);\nif (keyType !== 'zset') throw new Error(`Key ${finalKey} is not a geo set (type: ${keyType})`);","typeGuard":"type PipelineEntry = { result?: unknown; error?: string };\nfunction isValidGeoResult(entry: unknown): entry is { result: string[] } {\n  const e = entry as PipelineEntry;\n  return !e.error && Array.isArray(e.result) && e.result.every((v): v is string => typeof v === 'string');\n}","tryCatchPattern":"try {\n  const members = await geoSearchByBoxStrict(key, lon, lat, w, h, count);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Invalid GEOSEARCH result') {\n    logger.error({ key, err }, 'GEOSEARCH returned unexpected shape; serving empty result');\n    return [];\n  }\n  throw err;\n}","preventionTips":["Only pass keys populated with GEOADD; mixing ZADD/SET into the same key triggers pipeline errors.","Log the full pipeline response when this error fires to surface the embedded Redis error string.","Watch Upstash changelogs/status for pipeline response format changes.","Add a fixture-based test that mocks a malformed pipeline response."],"tags":["redis","validation","response-shape","upstash"],"backgroundTag":"unexpected-response-shape","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"}