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

  1. 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
  2. Map the status: 401/403 = key problem, 402/429 = entitlement or quota, 5xx = service issue — fix the matching layer rather than retrying blind
  3. For streaming calls dying mid-response, verify no intermediary buffers the stream and that the client reads the reader promptly
  4. 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

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


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/71c6c36858116736. Report an issue: GitHub.