koala73/worldmonitor · error
HTTP ${response.status}
Error message
HTTP ${response.status} What it means
fetchParts fetches multiple upstream URLs in parallel and throws a generic Error(`HTTP ${status}`) when any part returns a non-ok response (after billing denials are handled separately). The message is intentionally terse; the actual status code is embedded in the message and the specific upstream body is not surfaced.
Solutions
- Read the thrown message's status code and check the corresponding upstream endpoint's health/logs
- Fix the URL/path or credentials that produce the non-2xx status
- Add retry-with-backoff for transient 5xx statuses
- Consider enriching the error with the URL/status context at the call site to ease diagnosis
Example fix
// before
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// after
if (!response.ok) throw new Error(`HTTP ${response.status} from ${url}: ${await response.text().catch(() => '')}`); Defensive patterns
Strategy: retry
Validate before calling
// pre-flight reachability
const probe = await fetch(url, { method: 'HEAD' }); if (!probe.ok) throw new Error(`upstream ${url} unhealthy: ${probe.status}`); Type guard
null
Try / catch
try { return await fetchParts(urls, op); } catch (e) { if (/^HTTP 5\d\d$/.test(e.message)) return retryWithBackoff(() => fetchParts(urls, op), 3); throw e; } Prevention
- Include the URL and status in thrown errors for diagnosability
- Monitor upstream endpoint health and pin to versioned endpoint paths
- Retry only transient statuses (429, 5xx), never 4xx validation failures
- Set alerts on upstream 5xx rates
When it happens
Trigger: Any RPC registry tool that fans out to multiple upstream endpoints via fetchParts where one endpoint returns 404/500/502/503, or an auth-less URL (buildAuthHeaders returning null yields null parts, but a bad-token call yields 401). Timeouts are separately handled by AbortSignal.timeout(8000).
Common situations: Upstream endpoint path changed or removed (404); upstream service degraded (5xx); expired credentials returning 401; calling a URL built from stale config.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- ${label} HTTP ${response.status}
- relay returned ${resp.status}
- ${operation} HTTP ${status}: ${safeCode}
- Wikidata request failed with status ${response.status}
- relay returned ${resp.status}
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/f706b8908f0adc59.
Report an issue: GitHub.
Appendix: source
Thrown at api/mcp/registry/rpc-tools.ts:2480
}
const bbox = COUNTRY_BBOXES[code];
if (!bbox) return { error: `No airspace coverage for ${code}: that country has no bounding box in the dataset.` };
const box = countryBox(code);
const queryBoxes = box ? splitCountryBox(box) : [];
if (!queryBoxes.length) return { error: `No airspace coverage for ${code}: its full-longitude extent cannot scope a country flight query.` };
const [sw_lat, sw_lon, ne_lat, ne_lon] = bbox;
const type = String(params.type ?? 'all');
const UA = 'worldmonitor-mcp-edge/1.0';
const queries = queryBoxes.map(bounds =>
`sw_lat=${bounds.south}&sw_lon=${bounds.west}&ne_lat=${bounds.north}&ne_lon=${bounds.east}`);
async function fetchParts<T>(urls: string[], operation: string): Promise<(T | null)[]> {
const parts = await Promise.allSettled(urls.map(async url => {
const auth = await buildAuthHeaders(context, 'GET', url, null);
if (!auth) return null;
const response = await fetch(url, { headers: { ...auth, 'User-Agent': UA }, signal: AbortSignal.timeout(8_000) });
throwIfBillingDenial(response, operation);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json() as Promise<T>;
}));
// A failure in one half must not hide a billing denial in the other.
for (const part of parts) {
if (part.status === 'rejected' && part.reason instanceof BillingDenialError) throw part.reason;
}
return parts.map(part => {
if (part.status === 'rejected') throw part.reason;
return part.value;
});
}
const [civResult, milResult] = await Promise.allSettled([
type === 'military' ? Promise.resolve(null) : fetchParts<TrackAircraftResponse>(
queries.map(query => `${base}/api/aviation/v1/track-aircraft?${query}`), 'get-airspace-civilian'),
type === 'civilian' ? Promise.resolve(null) : fetchParts<ListMilitaryFlightsResponse>(
queries.map(query => `${base}/api/military/v1/list-military-flights?${query}&page_size=100`), 'get-airspace-military'),
]);View on GitHub (pinned to 7d06c8633d)