{"record":{"id":"011d408afe28aa75","repo":"koala73/worldmonitor","slug":"unavailable","errorCode":"unavailable","errorMessage":"Dashboard unavailable: ${boundedText(error.message, 160)} Reason: ${error.reason}.","messagePattern":"Dashboard unavailable: (.+?) Reason: (.+?)\\.","errorType":"error_code","errorClass":"SafeWebMcpError","httpStatus":null,"severity":"error","filePath":"src/services/webmcp.ts","lineNumber":783,"sourceCode":"      const reason = signal?.aborted\n        ? 'cancelled'\n        : classifyInvocationError(error);\n      reportWebMcpEvent(trackEvent, 'webmcp-tool-invoked', {\n        tool: name,\n        outcome: 'failure',\n        reason,\n      });\n      if (signal?.aborted) throwIfWebMcpAborted(signal);\n      if (error instanceof SafeWebMcpError) throw error;\n      if (isWebMcpAbortError(error)) throw error;\n      if (error instanceof DashboardBindingError) {\n        throw new SafeWebMcpError(\n          `Dashboard unavailable: ${boundedText(error.message, 160)} Reason: ${error.reason}.`,\n          'unavailable',\n        );\n      }\n      if (error instanceof DashboardPanelCatalogError) {\n        throw new SafeWebMcpError(error.message, 'validation');\n      }\n      if (error instanceof MissionPresetCatalogError) {\n        throw new SafeWebMcpError(error.message, 'validation');\n      }\n      throw new SafeWebMcpError(TOOL_FAILURE_MESSAGES[name]);\n    }\n  };\n}\n\nfunction enforceOutputBudget(value: unknown): void {\n  const serialized = JSON.stringify(value);\n  if (typeof serialized !== 'string' || serialized.length > MAX_OUTPUT_CHARS) {\n    throw new SafeWebMcpError('Tool output exceeded the safe output limit.');\n  }\n}\n\nfunction structuredResultReasons(result: Record<string, unknown>): string[] {\n  const reasons = typeof result.reason === 'string' ? [result.reason] : [];","sourceCodeStart":765,"sourceCodeEnd":801,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/src/services/webmcp.ts#L765-L801","documentation":"withInvocationLogging wraps every WorldMonitor WebMCP tool invocation. When the wrapped tool throws a DashboardUnavailableError, it is re-thrown as a SafeWebMcpError with code 'unavailable', embedding a bounded (160-char) version of the underlying error message plus the error's reason field. It signals that the dashboard SPA surface the tool needs to act on is not currently reachable, not that the tool arguments were wrong.","triggerScenarios":"Any WebMCP tool call (via `tools`) whose handler throws DashboardUnavailableError — e.g. the dashboard app instance is not mounted/initialized, or the SPA reports itself unavailable with a reason such as the page not being hydrated — at the moment the tool executes.","commonSituations":"Calling a dashboard tool before the WorldMonitor app finishes booting; the dashboard being torn down or navigated away; an internal app state machine marking itself unavailable with a specific reason after a failed init; embedded/iframe contexts where the SPA never mounted.","solutions":["Wait for the dashboard 'ready' signal (app mount/boot completion) before invoking WebMCP dashboard tools.","Read the `Reason:` suffix in the message — it is error.reason from DashboardUnavailableError and points at the exact unavailability cause.","Retry the tool call after a short delay if the dashboard was mid-boot; unavailability is often transient.","If reason indicates a permanent mount failure, fix the app bootstrap (missing container, JS init error) instead of retrying the tool."],"exampleFix":"// before\nconst ctx = await tools.call('get_dashboard_context');\n// after\nif (!dashboard.isReady()) await dashboard.whenReady();\ntry {\n  const ctx = await tools.call('get_dashboard_context');\n} catch (e) {\n  if (e.message.includes('Dashboard unavailable')) await dashboard.whenReady();\n}","handlingStrategy":"retry","validationCode":"function isDashboardReady(dashboard) {\n  return Boolean(dashboard && typeof dashboard.isReady === 'function' && dashboard.isReady());\n}","typeGuard":"function isDashboardUnavailableError(e) {\n  return e instanceof Error && e.message.startsWith('Dashboard unavailable:');\n}","tryCatchPattern":"try {\n  await tools.call('get_dashboard_context', {});\n} catch (e) {\n  if (isDashboardUnavailableError(e)) {\n    await dashboard.whenReady(); // or setTimeout retry with backoff\n    return retry();\n  }\n  throw e;\n}","preventionTips":["Gate all WebMCP dashboard tool calls behind the app's ready/booted signal.","Parse the 'Reason:' suffix to distinguish transient boot from permanent mount failure.","Use exponential backoff on retry instead of tight loops.","Log the reason field for diagnostics before retrying."],"tags":["webmcp","dashboard","lifecycle","unavailable"],"backgroundTag":"dashboard-unavailable","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}