NousResearch/hermes-agent · error
Hermes desktop bridge unavailable
Error message
Hermes desktop bridge unavailable
What it means
Thrown by pluginRest() in apps/desktop/src/hermes.ts:299 when `window.hermesDesktop.api` is not present — i.e. the code is not running inside the Hermes desktop shell (or the preload bridge has not been injected yet). All desktop REST traffic goes through the Electron preload bridge; in a plain browser tab, a web preview, or before bridge initialization there is no `api` function to route through.
Source
Thrown at apps/desktop/src/hermes.ts:299
function pluginPathSuffix(caller: string, path: string): string {
const suffix = path.startsWith('/') ? path : `/${path}`
if (suffix.split(/[?#]/, 1)[0].split('/').includes('..')) {
throw new Error(`${caller}: illegal path traversal in "${path}"`)
}
return suffix
}
/** The plugin REST door. Every call is scoped BY CONSTRUCTION to the plugin's
* own backend namespace — `path` is relative to `/api/plugins/<pluginId>`
* ('/board' → `/api/plugins/kanban/board`), so a plugin can't address another
* plugin's API or a core route through it. Profile-aware like every desktop
* REST call. Broader reach (core endpoints, another namespace) is the future
* declared-capability seam; today the namespace IS the boundary. */
export async function pluginRest<T>(pluginId: string, path: string, opts: PluginRestOptions = {}): Promise<T> {
if (!window.hermesDesktop?.api) {
throw new Error('Hermes desktop bridge unavailable')
}
const suffix = pluginPathSuffix('pluginRest', path)
return window.hermesDesktop.api<T>({
path: `/api/plugins/${pluginId}${suffix}`,
method: opts.method,
body: opts.body,
upload: opts.upload,
timeoutMs: opts.timeoutMs,
...profileScoped()
})
}
/** The plugin WebSocket door — the live twin of `pluginRest`, scoped the same
* way: `path` is relative to `/api/plugins/<pluginId>` ('/events' → the
* plugin's own event stream). Token-mode backends auth via the same query
* credential the app's own sockets use; OAuth remotes resolve null (callersView on GitHub (pinned to c896c09c42)
Solutions
- Guard the call: `if (!window.hermesDesktop?.api) { /* hide/disable the plugin REST feature */ }`.
- In tests, stub `window.hermesDesktop = { api: vi.fn() }` before invoking plugin code.
- If code must run in both environments, branch to the web app's HTTP client when the bridge is absent.
- Verify you are actually inside the desktop shell (window.hermesDesktop defined) before mounting plugin surfaces that need REST.
Example fix
// before
const board = await pluginRest('kanban', '/board')
// after
if (!window.hermesDesktop?.api) {
setUnavailable('Plugin API requires the Hermes desktop shell')
return
}
const board = await pluginRest('kanban', '/board') Defensive patterns
Strategy: type-guard
Type guard
function hasDesktopApi(w: Window): w is Window & { hermesDesktop: { api: <T>(r: unknown) => Promise<T> } } {
return typeof (w as any).hermesDesktop?.api === 'function'
} Try / catch
if (!hasDesktopApi(window)) { setFeatureUnavailable('requires the Hermes desktop shell'); return }
try { await pluginRest(id, '/board') } catch (e) { notifyError(e) } Prevention
- Check window.hermesDesktop?.api once at feature mount, not per call in a loop
- Stub the bridge in unit tests
- Keep pluginRest-touching components out of web/dashboard bundles
- Don't catch-and-ignore: absence of the bridge is an environment fact, model it as a disabled state
When it happens
Trigger: Calling pluginRest() from a component rendered in the web dashboard or a normal browser (no hermesDesktop preload); calling during app startup before the preload script exposes the bridge; running plugin code in a context where the sandboxed renderer did not receive contextBridge injection.
Common situations: A runtime plugin or shared module executed in both desktop and web builds; a unit test (jsdom) with no window.hermesDesktop stub; opening the renderer's index.html directly outside Electron; a corrupted or disabled preload bundle after an update.
Related errors
- Hermes Desktop bridge is unavailable
- Hermes Desktop bridge is unavailable
- Desktop preview browser bridge is unavailable
- Secure token storage is unavailable (no OS keyring service w
- Failed to encrypt the remote gateway token for secure storag
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/98df971f7438bc60.
Report an issue: GitHub.