koala73/worldmonitor · error · Error
HTTP ${res.status}
Error message
HTTP ${res.status} What it means
Thrown by McpDataPanel's widget-agent flow when POST widgetAgentUrl() with { prompt, mode: 'create', tier: 'pro' } returns a non-OK status or has no body: !res.ok || !res.body throws 'HTTP <status>'. Unlike the proxy endpoints, this is a raw fetch carrying explicit auth headers (X-Widget-Key, X-Pro-Key, or X-WorldMonitor-Key), so status codes here map to that gateway's own auth and tier rules, and a 200-with-no-body (stream killed by an intermediary) also trips it.
Source
Thrown at src/components/McpDataPanel.ts:208
try {
const testerKey = getBrowserTesterKey();
const widgetKey = getWidgetAgentKey();
const proKey = getProWidgetKey();
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
if (widgetKey) headers['X-Widget-Key'] = widgetKey;
if (proKey) headers['X-Pro-Key'] = proKey;
if (testerKey) headers['X-WorldMonitor-Key'] = testerKey;
const res = await fetch(widgetAgentUrl(), {
method: 'POST',
headers,
body: JSON.stringify({ prompt, mode: 'create', tier: 'pro' }),
signal: this.destroyController.signal.aborted
? this.destroyController.signal
: timeoutController.signal,
});
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buf = '';
let resultHtml = '';
let rendered = false;
while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
const lines = buf.split('\n');
buf = lines.pop() ?? '';
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
let event: { type: string; [k: string]: unknown };
try { event = JSON.parse(line.slice(6)); } catch { continue; }
View on GitHub (pinned to eeab0a219f)
Solutions
- Confirm which key header is actually being sent (widgetKey, proKey, or testerKey — only one path populates each) and that it is current; rotate/refresh it in settings
- Map the status: 401/403 = key problem, 402/429 = entitlement or quota, 5xx = service issue — fix the matching layer rather than retrying blind
- For streaming calls dying mid-response, verify no intermediary buffers the stream and that the client reads the reader promptly
- Catch this error in the panel's existing catch and surface the status to the user instead of a raw 'HTTP n' string
Example fix
// before
if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);
// after (separate the two failure modes and name the likely cause):
if (!res.ok) throw new Error(`HTTP ${res.status}${res.status === 401 || res.status === 403 ? ' (widget/pro key rejected — refresh keys)' : ''}`);
if (!res.body) throw new Error('HTTP 200 with no stream body (intermediary dropped the stream)'); Defensive patterns
Strategy: try-catch
Validate before calling
const hasAuth = Boolean(widgetKey || proKey || testerKey);
if (!hasAuth) { showError('Widget requires a widget, pro, or tester key'); return; } Type guard
function hasWidgetCredentials(opts: { widgetKey?: string; proKey?: string; testerKey?: string }): boolean { return Boolean(opts.widgetKey || opts.proKey || opts.testerKey); } Try / catch
catch (e) { if (e instanceof Error && /^HTTP \d+$/.test(e.message)) { const s = Number(e.message.slice(5)); if (s === 401 || s === 403) showError('Widget key rejected — refresh your key'); else if (s === 429) scheduleRetry(); else showError(`Agent service error (${s})`); } else throw e; } Prevention
- Send exactly one key header path and keep the key current — rotation silently breaks saved widgets
- Check !res.body separately from !res.ok: a 200 without a stream means an intermediary dropped it
- Honor the destroyController signal so panel teardown aborts the stream cleanly
When it happens
Trigger: Invalid or expired widget key (401/403 on X-Widget-Key); a pro key that lost its entitlement (402/403 tier gate — the request hardcodes tier: 'pro'); rate limiting (429); agent service 5xx; or a proxy that strips the streaming body so res.body is null despite 200.
Common situations: Shared/embedded widget links whose key was rotated or expired; a user's Pro subscription lapsing while a saved widget config keeps calling; corporate proxies buffering SSE and dropping the stream; the agent service redeploying.
Related errors
- HTTP ${resp.status}
- DNS ${recordType} lookup failed: HTTP ${response.status}
- DNS ${recordType} lookup failed: status ${data.Status}
- HTTP ${resp.status}
- HTTP ${resp.status}
AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21).
Data as JSON: /api/errors/71c6c36858116736.
Report an issue: GitHub.