NousResearch/hermes-agent · error

Desktop preview browser bridge is unavailable

Error message

Desktop preview browser bridge is unavailable

What it means

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.

Source

Thrown at apps/desktop/src/lib/local-preview.ts:151

    for (const attribute of Array.from(element.attributes)) {
      if (attribute.localName === 'href' || attribute.localName === 'ping') {
        element.removeAttributeNode(attribute)
      }
    }
  })
  const policy = document.createElement('meta')
  policy.httpEquiv = 'Content-Security-Policy'
  policy.content = csp
  document.head.prepend(policy)

  return `<!doctype html>${document.documentElement.outerHTML}`
}

export async function openPreviewTargetInBrowser(target: PreviewTarget): Promise<void> {
  const bridge = window.hermesDesktop

  if (!bridge?.openPreviewInBrowser) {
    throw new Error('Desktop preview browser bridge is unavailable')
  }

  const dataUrl = target.dataUrl && validatedRemoteHtmlDataUrl(target.dataUrl)

  if (!dataUrl) {
    if (target.transient) {
      throw new Error('Remote HTML preview could not be loaded')
    }

    await bridge.openPreviewInBrowser(target.url)

    return
  }

  if (!bridge.saveImageBuffer) {
    throw new Error('Desktop preview buffer bridge is unavailable')
  }

View on GitHub (pinned to c896c09c42)

Solutions

  1. Gate the 'open in browser' affordance: hide it when `!window.hermesDesktop?.openPreviewInBrowser`.
  2. Update and restart the desktop app so the preload exposes the capability.
  3. In tests, stub openPreviewInBrowser (and saveImageBuffer for data-url targets) on the bridge.
  4. Fall back to rendering the preview in-app (the embedded preview path) when the browser bridge is absent.

Example fix

// before
await openPreviewTargetInBrowser(target)

// after
if (!window.hermesDesktop?.openPreviewInBrowser) {
  showPreviewInline(target) // in-app fallback instead of hard failure
  return
}
await openPreviewTargetInBrowser(target)
Defensive patterns

Strategy: type-guard

Validate before calling

function canOpenPreviewInBrowser(): boolean {
  return typeof window.hermesDesktop?.openPreviewInBrowser === 'function'
}

Type guard

function hasPreviewBridge(w: Window): w is Window & { hermesDesktop: { openPreviewInBrowser: (url: string) => Promise<void>; saveImageBuffer?: (b: Uint8Array, ext: string) => Promise<string | null> } } {
  return typeof (w as any).hermesDesktop?.openPreviewInBrowser === 'function'
}

Try / catch

if (!hasPreviewBridge(window)) { showPreviewInline(target); return }
try { await openPreviewTargetInBrowser(target) } catch (e) { if (e instanceof Error && e.message.includes('bridge is unavailable')) showPreviewInline(target) else throw e }

Prevention

When it happens

Trigger: 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.

Common situations: Version skew between a newly updated renderer and an older installed shell (restart required); preview UI mounted outside the desktop app; feature-gated preloads.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/bf70bf690f668e82. Report an issue: GitHub.