koala73/worldmonitor · critical
Redis unavailable
Error message
Redis unavailable
What it means
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.
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.
Example fix
// before
const result = await geoSearchByBoxStrict('vessels:geo', lon, lat, 50, 50, 100);
// after
if (!process.env.UPSTASH_REDIS_REST_URL || !process.env.UPSTASH_REDIS_REST_TOKEN) {
return []; // degraded mode
}
const result = await geoSearchByBoxStrict('vessels:geo', lon, lat, 50, 50, 100); Defensive patterns
Strategy: fallback
Validate before calling
export function hasRedisConfig(): boolean {
return Boolean(process.env.UPSTASH_REDIS_REST_URL && process.env.UPSTASH_REDIS_REST_TOKEN);
} Type guard
const isRedisConfigured = (): boolean => typeof process.env.UPSTASH_REDIS_REST_URL === 'string' && process.env.UPSTASH_REDIS_REST_URL.length > 0 && typeof process.env.UPSTASH_REDIS_REST_TOKEN === 'string' && process.env.UPSTASH_REDIS_REST_TOKEN.length > 0;
Try / catch
let results: string[];
try {
results = await geoSearchByBoxStrict(key, lon, lat, w, h, count);
} catch (err) {
if (err instanceof Error && err.message === 'Redis unavailable') {
results = []; // degraded mode without Redis
} else {
throw err;
}
} Prevention
- 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.
When it happens
Trigger: 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().
Common situations: 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.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- cache_all_null
- cache_all_null
- Failed to enqueue scenario job
- Invalid GEOSEARCH result
- Redis command error
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-09-22).
Data as JSON: /api/errors/bcdaef7f5bdc493b.
Report an issue: GitHub.
Appendix: source
Thrown at server/_shared/redis.ts:1213
} catch (error) {
logCacheReadError(args[0], error);
return [];
}
}
export async function getHashFieldsBatch(...args: Parameters<typeof getHashFieldsBatchStrict>): Promise<Map<string, string>> {
try {
return await getHashFieldsBatchStrict(...args);
} catch (error) {
logCacheReadError(args[0], error);
return new Map();
}
}
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;
}
View on GitHub (pinned to e586b8b4b8)