{"record":{"id":"e5f79483f26a4b64","repo":"moeru-ai/airi","slug":"plugin-host-debug-bridge-is-not-available-in-this","errorCode":null,"errorMessage":"Plugin host debug bridge is not available in this runtime.","messagePattern":"Plugin host debug bridge is not available in this runtime\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"packages/stage-ui/src/stores/devtools/plugin-host-debug.ts","lineNumber":141,"sourceCode":"    refreshedAt.value = snapshot.refreshedAt\n  }\n\n  async function withBridge<T>(run: (activeBridge: PluginHostDebugBridge) => Promise<T>) {\n    // Single guard/flow wrapper for every debug action.\n    //\n    // What it does:\n    // 1) Runtime gate: blocks actions until bridge is registered.\n    // 2) Loading lifecycle: toggles `loading` in a centralized place.\n    // 3) Error normalization: stores user-facing error text for the debug page.\n    //\n    // Why debug store needs this:\n    // - Debug actions are async IPC calls and may fail for runtime/setup reasons.\n    // - A shared wrapper avoids duplicated try/catch/loading logic across each action.\n    // - It gives deterministic UI behavior (same errors/spinner semantics for all commands).\n    if (!bridge.value) {\n      const message = 'Plugin host debug bridge is not available in this runtime.'\n      error.value = message\n      throw new Error(message)\n    }\n\n    loading.value = true\n    clearError()\n    try {\n      return await run(bridge.value)\n    }\n    catch (cause) {\n      error.value = errorMessageFrom(cause) ?? 'Plugin host debug request failed.'\n      throw cause\n    }\n    finally {\n      loading.value = false\n    }\n  }\n\n  async function refreshRegistry() {\n    const nextRegistry = await withBridge(activeBridge => activeBridge.list())","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/moeru-ai/airi/blob/f679616c34f1cf6d282c8d64264242af944b3fed/packages/stage-ui/src/stores/devtools/plugin-host-debug.ts#L123-L159","documentation":"The shared plugin-host devtools store wraps every action in withBridge, which requires a PluginHostDebugBridge registered via setBridge by the renderer bootstrap (it wires Eventa invoke functions from the Electron main process). When bridge.value is unset — because the runtime never ran that bootstrap or the page opened before registration — every debug action throws this availability error and records it in error.value.","triggerScenarios":"Opening the plugin-host devtools page in a non-Electron runtime (stage-web, unit tests) where no IPC bridge exists; calling debug actions before the renderer bootstrap called setBridge; hot-reload losing the bridge registration; opening the page in an environment whose main process lacks the plugin host service.","commonSituations":"Reusing the shared devtools page outside stage-tamagotchi; race between route mount and bridge registration during startup; tests instantiating the store without registering a mock bridge.","solutions":["Use the plugin-host devtools page inside stage-tamagotchi (Electron), where the bridge is registered.","Gate actions/UI on the store's isAvailable computed instead of calling and catching.","Ensure the renderer bootstrap calls setBridge before devtools routes can mount.","In tests, register a mock bridge via setBridge before invoking actions."],"exampleFix":"// before\nasync function onInspect() {\n  await pluginHostDebug.refreshInspection()\n}\n\n// after\nasync function onInspect() {\n  if (!pluginHostDebug.isAvailable) {\n    toast.info('Plugin host debug is only available in the desktop runtime')\n    return\n  }\n  await pluginHostDebug.refreshInspection()\n}","handlingStrategy":"type-guard","validationCode":"if (!pluginHostDebug.isAvailable) {\n  toast.info('Plugin host debug requires the desktop (Electron) runtime')\n  return\n}\nawait pluginHostDebug.refreshRegistry()","typeGuard":"// The store already exposes the guard: isAvailable === Boolean(bridge)\nfunction canUsePluginHostDebug(store: ReturnType<typeof usePluginHostDebugStore>): boolean {\n  return store.isAvailable\n}","tryCatchPattern":"try {\n  await pluginHostDebug.refreshInspection()\n}\ncatch (error) {\n  // error.value already carries the user-facing text set by withBridge\n  if (!pluginHostDebug.isAvailable)\n    return // expected outside Electron; do not alarm the user\n  console.error(errorMessageFrom(error))\n}","preventionTips":["Gate devtools routes/actions on isAvailable rather than calling and catching.","Ensure the renderer bootstrap registers the bridge (setBridge) before devtools mount.","Register a mock bridge in tests via setBridge before invoking actions.","After HMR, re-run the bootstrap that calls setBridge if actions start failing."],"tags":["devtools","ipc","electron","runtime","bridge"],"backgroundTag":"ipc-bridge-unavailable","analyzedSha":"f679616c34f1cf6d282c8d64264242af944b3fed","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}