koala73/worldmonitor · error

Invalid GEOSEARCH result

Error message

Invalid GEOSEARCH result

What it means

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.

Solutions

  1. Log data[0].error to see the underlying Redis error and fix the key's type (delete and repopulate with GEOADD).
  2. Confirm the key holds a geospatial set (TYPE <key> == zset populated via GEOADD) and that the name matches after prefixKey().
  3. Retry on transient upstream issues; check Upstash status/incidents if shapes changed suddenly.
  4. Harden the caller to catch this error and return an empty result set for degraded operation.

Example fix

// before
const members = await geoSearchByBoxStrict(key, lon, lat, w, h, 10);
// after
let members: string[] = [];
try {
  members = await geoSearchByBoxStrict(key, lon, lat, w, h, 10);
} catch (err) {
  if (err instanceof Error && err.message === 'Invalid GEOSEARCH result') {
    console.error('geoSearchByBoxStrict failed for key', key, err);
  } else throw err;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check the key type before GEOSEARCH (strict helper assumes a geo set)
const keyType = await redisType(finalKey);
if (keyType !== 'zset') throw new Error(`Key ${finalKey} is not a geo set (type: ${keyType})`);

Type guard

type PipelineEntry = { result?: unknown; error?: string };
function isValidGeoResult(entry: unknown): entry is { result: string[] } {
  const e = entry as PipelineEntry;
  return !e.error && Array.isArray(e.result) && e.result.every((v): v is string => typeof v === 'string');
}

Try / catch

try {
  const members = await geoSearchByBoxStrict(key, lon, lat, w, h, count);
} catch (err) {
  if (err instanceof Error && err.message === 'Invalid GEOSEARCH result') {
    logger.error({ key, err }, 'GEOSEARCH returned unexpected shape; serving empty result');
    return [];
  }
  throw err;
}

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22). Data as JSON: /api/errors/e57c6ba7a8546a39. Report an issue: GitHub.

Appendix: source

Thrown at server/_shared/redis.ts:1228

export async function geoSearchByBoxStrict(key: string, lon: number, lat: number, widthKm: number, heightKm: number, count: number, raw = false): Promise<string[]> {
  const url = process.env.UPSTASH_REDIS_REST_URL;
  const token = process.env.UPSTASH_REDIS_REST_TOKEN;
  if (!url || !token) throw new Error('Redis unavailable');
  const finalKey = raw ? key : prefixKey(key);
  const pipeline = [['GEOSEARCH', finalKey, 'FROMLONLAT', String(lon), String(lat), 'BYBOX', String(widthKm), String(heightKm), 'km', 'ASC', 'COUNT', String(count)]];
  const resp = await fetch(`${url}/pipeline`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      'User-Agent': 'worldmonitor-server/1.0 (redis)',
    },
    body: JSON.stringify(pipeline),
    signal: AbortSignal.timeout(REDIS_PIPELINE_TIMEOUT_MS),
  });
  if (!resp.ok) throw new Error(`Redis HTTP ${resp.status}`);
  const data = (await resp.json()) as Array<{ result?: string[]; error?: string }>;
  if (data[0]?.error || !Array.isArray(data[0]?.result) || !data[0].result.every(value => typeof value === 'string')) throw new Error('Invalid GEOSEARCH result');
  return data[0].result;
}

export async function getHashFieldsBatchStrict(
  key: string,
  fields: string[],
  raw = false,
  timeoutMs?: number,
): Promise<Map<string, string>> {
  const result = new Map<string, string>();
  if (fields.length === 0) return result;
  const url = process.env.UPSTASH_REDIS_REST_URL;
  const token = process.env.UPSTASH_REDIS_REST_TOKEN;
  if (!url || !token) throw new Error('Redis unavailable');
  const finalKey = raw ? key : prefixKey(key);
  const pipeline = [['HMGET', finalKey, ...fields]];
  const resp = await fetch(`${url}/pipeline`, {
    method: 'POST',

View on GitHub (pinned to e586b8b4b8)