CloakHQ/CloakBrowser · error · Error

GeoIP resolution failed: could not determine ${missing.join(

Error message

GeoIP resolution failed: could not determine ${missing.join(" and ")}

What it means

maybeResolveGeoip finished but could not determine one or both of timezone and locale: neither the caller-supplied values nor the GeoIP result produced them. The message names exactly which fields are missing.

Source

Thrown at js/src/geoip.ts:484

  // When both tz/locale are explicit, resolve the exit IP for WebRTC — but only
  // with a proxy. With no proxy the WebRTC IP would just be the real connection
  // IP the site already sees (a no-op), so skip the third-party echo call.
  if (timezone && locale) {
    if (!proxyUrl) return { timezone, locale };
    const timeoutMs = getGeoipTimeoutMs();
    const exitIp = await resolveExitIp(proxyUrl, timeoutMs) ?? undefined;
    return { timezone, locale, exitIp };
  }

  const { timezone: geoTz, locale: geoLocale, exitIp: geoExitIp } = await resolveProxyGeo(proxyUrl);
  const resolvedTimezone = timezone ?? geoTz ?? undefined;
  const resolvedLocale = locale ?? geoLocale ?? undefined;
  const missing = [
    resolvedTimezone ? null : "timezone",
    resolvedLocale ? null : "locale",
  ].filter((value): value is string => value !== null);
  if (missing.length > 0) {
    throw new Error(`GeoIP resolution failed: could not determine ${missing.join(" and ")}`);
  }
  return {
    timezone: resolvedTimezone,
    locale: resolvedLocale,
    exitIp: geoExitIp ?? undefined,
  };
}

/**
 * Append `--fingerprint-webrtc-ip=<exitIp>` unless the user already set the flag.
 * The exit IP comes free from the geoip lookup; it spoofs the WebRTC IP to the
 * egress IP. No-op when there is no exit IP or the flag is already present. This
 * rule must stay identical across every launch path, so it lives in one place.
 */
export function appendWebrtcExitIp(
  args: string[] | undefined,
  exitIp: string | undefined,
): string[] | undefined {

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Pass explicit timezone and locale when you know them (they take precedence and bypass the error)
  2. Update or extend COUNTRY_LOCALE_MAP / use a newer GeoIP database covering the exit IP
  3. If locale cannot be inferred, default it (e.g. 'en-US') instead of failing

Example fix

// before
const { timezone, locale } = await maybeResolveGeoip({ ip, dbPath });

// after
const { timezone, locale } = await maybeResolveGeoip({
  ip,
  dbPath,
  timezone: 'America/New_York', // known ahead of time
  locale: 'en-US',
});
Defensive patterns

Strategy: fallback

Validate before calling

const desired = { timezone: 'America/New_York', locale: 'en-US' };
const needGeoip = !desired.timezone || !desired.locale;
if (!needGeoip) { /* skip maybeResolveGeoip entirely */ }

Type guard

function missingGeoFields(e: unknown): string[] | null {
  if (!(e instanceof Error)) return null;
  const m = e.message.match(/could not determine (.+)$/);
  return m ? m[1].split(' and ') : null;
}

Try / catch

try { return await maybeResolveGeoip(opts); }
catch (e) {
  const missing = missingGeoFields(e);
  if (missing) {
    return { timezone: opts.timezone ?? 'UTC', locale: opts.locale ?? 'en-US', exitIp: undefined };
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling maybeResolveGeoip without timezone/locale while the GeoIP lookup returned a country with no entry in COUNTRY_LOCALE_MAP (locale missing), or the reader result lacked a timezone field; also when GeoIP was skipped and no explicit values were given.

Common situations: Exotic IPs (satellite/VPN providers) with sparse MaxMind data, countries not covered by the locale map, or callers assuming GeoIP always succeeds and omitting explicit timezone/locale options.

Related errors


AI-assisted analysis of CloakHQ/CloakBrowser@d6bad5de26 (2026-08-28). Data as JSON: /api/errors/6b35adaafd879062. Report an issue: GitHub.