{"record":{"id":"4ca722721e64ed87","repo":"koala73/worldmonitor","slug":"get-country-intel-brief-http-res-status-code","errorCode":null,"errorMessage":"get-country-intel-brief HTTP ${res.status}${code ? `: ${code}` : ''}","messagePattern":"get-country-intel-brief HTTP (.+?)(.+?)` : ''\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/mcp/registry/rpc-tools.ts","lineNumber":1056,"sourceCode":"      });\n      if (!res.ok) {\n        throwIfBillingDenial(res, 'get-country-intel-brief');\n        // Surface the gateway's error code in the thrown message so Sentry\n        // groups the failure by root cause, not just status. Body reads are\n        // best-effort; a read failure must not mask the HTTP status.\n        const detail = await res.text().catch(() => '');\n        let code = '';\n        // `error` is usually a string (for example,\n        // `invalid_internal_mcp_signature`), but stringify non-string shapes so\n        // object envelopes remain readable. Bound both paths so Sentry titles\n        // cannot bloat on a long body.\n        try {\n          const error = (JSON.parse(detail) as { error?: unknown }).error ?? '';\n          code = (typeof error === 'string' ? error : JSON.stringify(error)).slice(0, 120);\n        } catch {\n          code = detail.replace(/<[^>]*>/g, ' ').replace(/\\s+/g, ' ').trim().slice(0, 120);\n        }\n        throw new Error(`get-country-intel-brief HTTP ${res.status}${code ? `: ${code}` : ''}`);\n      }\n      const result = await res.json() as Record<string, unknown>;\n      const resultSources = collectMcpBriefSources(Array.isArray(result.sources) ? result.sources as DigestItemForBrief[] : [], 6);\n      // groundingStories stays [] when the 2 s digest fetch failed above, which\n      // is the honest signal: the brief was written without that grounding.\n      return { ...result, sources: resultSources.length > 0 ? resultSources : sources, groundingStories };\n    },\n    // METHOD DRIFT: _execute POSTs above but OpenAPI declares only GET on this\n    // path (verified against docs/api/IntelligenceService.openapi.json). The\n    // gateway routes by path, not method, so POST works at runtime. We declare\n    // GET here because OpenAPI is the parity test's source-of-truth — fixing\n    // the spec to add POST (or migrating the handler to GET) is out of scope.\n    _apiPaths: [\n      \"GET /api/intelligence/v1/get-country-intel-brief\",\n    ],\n  },\n  {\n    name: 'get_country_risk',","sourceCodeStart":1038,"sourceCodeEnd":1074,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/api/mcp/registry/rpc-tools.ts#L1038-L1074","documentation":"Thrown by the get_country_intel_brief MCP tool when the downstream /api/intelligence/v1/get-country-intel-brief endpoint returned a non-OK HTTP status. Before this throw, the code checks for a BillingDenialError (re-thrown separately so billing contract is preserved), then extracts a bounded error detail (max 120 chars) from the JSON body's error field or falls back to stripped HTML/text. The message includes the HTTP status and the truncated code so Sentry titles stay bounded.","triggerScenarios":"The country intel brief handler returned 4xx/5xx — common causes: 401/403 (auth/HMAC failure on the gateway RPC), 429 (rate limit), 500 (LLM provider failure during brief generation), 502/504 (upstream LLM timeout), or a 400 (invalid country_code). The billing-denial path (402 with billing code) is handled earlier and never reaches this throw.","commonSituations":"LLM provider outage or rate limit causing the brief generation to fail; an expired or invalid MCP signature/HMAC causing 401/403; an unsupported country_code causing a 400; a gateway deployment issue where the route is temporarily unavailable.","solutions":["Read the HTTP status from the error message: 401/403 — check auth context and HMAC signing; 429 — retry with backoff; 5xx — retry after a delay (LLM provider issue); 400 — verify country_code is a valid ISO 3166-1 alpha-2 code.","Retry with exponential backoff for 429 and 5xx — these are typically transient.","Verify the buildAuthHeaders context (proxy_id, secret) is valid and not expired if you see 401/403.","Call a different country_code to determine if the failure is country-specific or systemic."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Validate inputs and auth before calling get_country_intel_brief\nif (!/^[A-Z]{2}$/.test(countryCode)) {\n  throw new Error('country_code must be a valid ISO 3166-1 alpha-2 code');\n}\n// Verify auth context is not expired before the call\nif (context.expiresAt && Date.now() > context.expiresAt) {\n  throw new Error('Auth context expired; refresh before calling');\n}","typeGuard":"function isCountryIntelBriefHttpError(e: unknown): e is { status: number } & Error {\n  return e instanceof Error && /get-country-intel-brief HTTP \\d+/.test(e.message);\n}","tryCatchPattern":"try {\n  const brief = await callMcpTool('get_country_intel_brief', { country_code: 'US' });\n} catch (e) {\n  if (e instanceof Error) {\n    const status = e.message.match(/HTTP (\\d+)/)?.[1];\n    if (status === '429' || (status && Number(status) >= 500)) {\n      await exponentialBackoffRetry(); // transient\n    } else if (status === '401' || status === '403') {\n      refreshAuthContext(); // auth issue\n    } else {\n      throw e; // 400 etc — input problem\n    }\n  } else throw e;\n}","preventionTips":["Validate country_code is a real ISO 3166-1 alpha-2 code before calling.","Ensure the auth context (proxy_id, secret) is fresh and the HMAC signature is valid.","Implement exponential backoff for 429/5xx responses — the LLM provider may be rate-limited."],"tags":["mcp","http","country-intel-brief","upstream-failure","llm"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}