{"record":{"id":"bf70bf690f668e82","repo":"NousResearch/hermes-agent","slug":"desktop-preview-browser-bridge-is-unavailable","errorCode":null,"errorMessage":"Desktop preview browser bridge is unavailable","messagePattern":"Desktop preview browser bridge is unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/src/lib/local-preview.ts","lineNumber":151,"sourceCode":"    for (const attribute of Array.from(element.attributes)) {\n      if (attribute.localName === 'href' || attribute.localName === 'ping') {\n        element.removeAttributeNode(attribute)\n      }\n    }\n  })\n  const policy = document.createElement('meta')\n  policy.httpEquiv = 'Content-Security-Policy'\n  policy.content = csp\n  document.head.prepend(policy)\n\n  return `<!doctype html>${document.documentElement.outerHTML}`\n}\n\nexport async function openPreviewTargetInBrowser(target: PreviewTarget): Promise<void> {\n  const bridge = window.hermesDesktop\n\n  if (!bridge?.openPreviewInBrowser) {\n    throw new Error('Desktop preview browser bridge is unavailable')\n  }\n\n  const dataUrl = target.dataUrl && validatedRemoteHtmlDataUrl(target.dataUrl)\n\n  if (!dataUrl) {\n    if (target.transient) {\n      throw new Error('Remote HTML preview could not be loaded')\n    }\n\n    await bridge.openPreviewInBrowser(target.url)\n\n    return\n  }\n\n  if (!bridge.saveImageBuffer) {\n    throw new Error('Desktop preview buffer bridge is unavailable')\n  }\n","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/src/lib/local-preview.ts#L133-L169","documentation":"Thrown by openPreviewTargetInBrowser() in apps/desktop/src/lib/local-preview.ts:151 when `window.hermesDesktop.openPreviewInBrowser` is missing. Opening a preview target in the user's real browser is an Electron-main-process capability (shell.openExternal / staged file opening) injected via preload; without it — older shell, web context, or stripped preload — the function refuses up front rather than half-executing.","triggerScenarios":"Clicking 'open in browser' on a preview card in a web build or non-Electron context; running on an older desktop shell that predates the openPreviewInBrowser IPC; a bridge stub in tests lacking the method.","commonSituations":"Version skew between a newly updated renderer and an older installed shell (restart required); preview UI mounted outside the desktop app; feature-gated preloads.","solutions":["Gate the 'open in browser' affordance: hide it when `!window.hermesDesktop?.openPreviewInBrowser`.","Update and restart the desktop app so the preload exposes the capability.","In tests, stub openPreviewInBrowser (and saveImageBuffer for data-url targets) on the bridge.","Fall back to rendering the preview in-app (the embedded preview path) when the browser bridge is absent."],"exampleFix":"// before\nawait openPreviewTargetInBrowser(target)\n\n// after\nif (!window.hermesDesktop?.openPreviewInBrowser) {\n  showPreviewInline(target) // in-app fallback instead of hard failure\n  return\n}\nawait openPreviewTargetInBrowser(target)","handlingStrategy":"type-guard","validationCode":"function canOpenPreviewInBrowser(): boolean {\n  return typeof window.hermesDesktop?.openPreviewInBrowser === 'function'\n}","typeGuard":"function hasPreviewBridge(w: Window): w is Window & { hermesDesktop: { openPreviewInBrowser: (url: string) => Promise<void>; saveImageBuffer?: (b: Uint8Array, ext: string) => Promise<string | null> } } {\n  return typeof (w as any).hermesDesktop?.openPreviewInBrowser === 'function'\n}","tryCatchPattern":"if (!hasPreviewBridge(window)) { showPreviewInline(target); return }\ntry { await openPreviewTargetInBrowser(target) } catch (e) { if (e instanceof Error && e.message.includes('bridge is unavailable')) showPreviewInline(target) else throw e }","preventionTips":["Render 'open in browser' buttons only when the capability exists","Update and restart the desktop shell together with the renderer","Provide an in-app preview fallback so absence of the bridge degrades gracefully","Stub both openPreviewInBrowser and saveImageBuffer in tests when exercising data-url targets"],"tags":["desktop","electron","preview","bridge","capability"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}