{"record":{"id":"71c6c36858116736","repo":"koala73/worldmonitor","slug":"http-res-status","errorCode":null,"errorMessage":"HTTP ${res.status}","messagePattern":"HTTP \\$\\{res\\.status\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/components/McpDataPanel.ts","lineNumber":208,"sourceCode":"\n    try {\n      const testerKey = getBrowserTesterKey();\n      const widgetKey = getWidgetAgentKey();\n      const proKey = getProWidgetKey();\n      const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n      if (widgetKey) headers['X-Widget-Key'] = widgetKey;\n      if (proKey) headers['X-Pro-Key'] = proKey;\n      if (testerKey) headers['X-WorldMonitor-Key'] = testerKey;\n      const res = await fetch(widgetAgentUrl(), {\n        method: 'POST',\n        headers,\n        body: JSON.stringify({ prompt, mode: 'create', tier: 'pro' }),\n        signal: this.destroyController.signal.aborted\n          ? this.destroyController.signal\n          : timeoutController.signal,\n      });\n\n      if (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);\n\n      const reader = res.body.getReader();\n      const decoder = new TextDecoder();\n      let buf = '';\n      let resultHtml = '';\n      let rendered = false;\n\n      while (true) {\n        const { done, value } = await reader.read();\n        if (done) break;\n        buf += decoder.decode(value, { stream: true });\n        const lines = buf.split('\\n');\n        buf = lines.pop() ?? '';\n        for (const line of lines) {\n          if (!line.startsWith('data: ')) continue;\n          let event: { type: string; [k: string]: unknown };\n          try { event = JSON.parse(line.slice(6)); } catch { continue; }\n","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/koala73/worldmonitor/blob/eeab0a219fce0f02a00603b532dbae9041b934ac/src/components/McpDataPanel.ts#L190-L226","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nif (!res.ok || !res.body) throw new Error(`HTTP ${res.status}`);\n\n// after (separate the two failure modes and name the likely cause):\nif (!res.ok) throw new Error(`HTTP ${res.status}${res.status === 401 || res.status === 403 ? ' (widget/pro key rejected — refresh keys)' : ''}`);\nif (!res.body) throw new Error('HTTP 200 with no stream body (intermediary dropped the stream)');","handlingStrategy":"try-catch","validationCode":"const hasAuth = Boolean(widgetKey || proKey || testerKey);\nif (!hasAuth) { showError('Widget requires a widget, pro, or tester key'); return; }","typeGuard":"function hasWidgetCredentials(opts: { widgetKey?: string; proKey?: string; testerKey?: string }): boolean { return Boolean(opts.widgetKey || opts.proKey || opts.testerKey); }","tryCatchPattern":"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; }","preventionTips":["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"],"tags":["network","http-status","mcp","widget-agent","streaming","auth"],"backgroundTag":"http-error-status","analyzedSha":"eeab0a219fce0f02a00603b532dbae9041b934ac","analyzedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}