NousResearch/hermes-agent · error · Error

Desktop bridge unavailable

Error message

Desktop bridge unavailable

What it means

Thrown by openHtmlInBrowser in the desktop right-rail artifact preview. It composes an HTML document and needs the Electron preload bridge (window.hermesDesktop) to write a temp file and hand a file:// URL to the OS browser — a blob/data URL cannot cross into the default browser. The error means the bridge object or one of its required methods (saveImageBuffer, openExternal) is missing, i.e. the code is running outside the expected Electron preload context or against an older preload that lacks those methods.

Source

Thrown at apps/desktop/src/app/chat/right-rail/preview-artifact.tsx:53

  }

  return [
    '<!doctype html>',
    '<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">',
    '<style>body{margin:0;font-family:system-ui,sans-serif}</style></head><body>',
    content,
    '</body></html>'
  ].join('\n')
}

/** Write the composed document to a real temp file through the existing
 *  buffer-save IPC, then hand it to the OS browser. A blob/data URL can't
 *  cross into the OS default browser, so a file on disk is the honest path. */
async function openHtmlInBrowser(content: string): Promise<void> {
  const bridge = window.hermesDesktop

  if (!bridge?.saveImageBuffer || !bridge.openExternal) {
    throw new Error('Desktop bridge unavailable')
  }

  const bytes = new TextEncoder().encode(composeArtifactHtml(content))
  const path = await bridge.saveImageBuffer(bytes, '.html')

  if (!path) {
    throw new Error('Could not write artifact file')
  }

  const fileUrl = `file://${path.startsWith('/') ? '' : '/'}${path.replace(/\\/g, '/')}`

  if (bridge.openPreviewInBrowser) {
    await bridge.openPreviewInBrowser(fileUrl)

    return
  }

  await bridge.openExternal(fileUrl)

View on GitHub (pinned to c896c09c42)

Solutions

  1. Ensure the renderer runs inside the Electron app with the current preload script that installs window.hermesDesktop (saveImageBuffer + openExternal).
  2. If running in a plain browser context, gate the 'open in browser' button behind a bridge availability check instead of calling openHtmlInBrowser.
  3. Rebuild/reinstall the desktop app so preload and renderer versions match.
  4. In tests, stub window.hermesDesktop with the required methods.

Example fix

// before
const bridge = window.hermesDesktop
if (!bridge?.saveImageBuffer || !bridge.openExternal) {
  throw new Error('Desktop bridge unavailable')
}

// after — feature-detect at the UI layer and disable the action
const bridgeAvailable = Boolean(
  window.hermesDesktop?.saveImageBuffer && window.hermesDesktop?.openExternal
)
// render the button only when bridgeAvailable; keep the throw as an invariant guard
Defensive patterns

Strategy: type-guard

Validate before calling

const bridge = window.hermesDesktop
const canOpenInBrowser = Boolean(bridge?.saveImageBuffer && bridge?.openExternal)
if (!canOpenInBrowser) {
  // hide/disable the 'open in browser' action instead of throwing at click time
}

Type guard

interface DesktopBridge {
  saveImageBuffer(bytes: Uint8Array, ext: string): Promise<string>
  openExternal(url: string): Promise<void>
  openPreviewInBrowser?(url: string): Promise<void>
}
const isDesktopBridge = (b: unknown): b is DesktopBridge =>
  typeof b === 'object' && b !== null &&
  typeof (b as DesktopBridge).saveImageBuffer === 'function' &&
  typeof (b as DesktopBridge).openExternal === 'function'

Try / catch

try {
  await openHtmlInBrowser(content)
} catch (error) {
  if (error instanceof Error && error.message === 'Desktop bridge unavailable') {
    // degrade gracefully: fall back to in-pane HTML rendering
    renderInline(content)
  } else {
    throw error
  }
}

Prevention

When it happens

Trigger: Rendering the artifact 'open in browser' action in a non-Electron host (plain browser, web dashboard, tests/jsdom) where window.hermesDesktop is undefined; an Electron runtime whose preload script predates the saveImageBuffer bridge method; contextIsolation/sandbox settings preventing the bridge from being exposed on window.

Common situations: Reusing desktop components in the web dashboard where the preload bridge does not exist; upgrading the renderer without upgrading the Electron main/preload bundle; running component tests without mocking window.hermesDesktop.

Related errors


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