NousResearch/hermes-agent · error

Hermes Desktop bridge is unavailable

Error message

Hermes Desktop bridge is unavailable

What it means

Thrown by desktopApi() in apps/desktop/src/lib/desktop-git.ts:25 when `window.hermesDesktop` is absent. desktop-git routes git operations through the Electron bridge (`desktop.api`) — locally via window.hermesDesktop.git, remotely via the dashboard REST /api/git/* mirror — but both shapes still need the base desktop bridge to make the api call. Outside the desktop shell there is no transport at all.

Source

Thrown at apps/desktop/src/lib/desktop-git.ts:25

  HermesReviewList,
  HermesReviewShipInfo
} from '@/global'

import { desktopFsProfile, isDesktopFsRemoteMode } from './desktop-fs'

// Remote-aware git facade. Locally the desktop runs git through Electron
// (window.hermesDesktop.git); on a remote gateway that's the wrong filesystem,
// so we mirror the same surface over the dashboard REST API (/api/git/*) — the
// coding rail, worktree lanes, review pane, and branch ops then act on the
// BACKEND repo where sessions actually run. Mirrors desktop-fs.ts.

type GitBridge = NonNullable<NonNullable<Window['hermesDesktop']>['git']>

function desktopApi<T>(path: string, body?: Record<string, unknown>): Promise<T> {
  const desktop = window.hermesDesktop

  if (!desktop) {
    throw new Error('Hermes Desktop bridge is unavailable')
  }

  return desktop.api<T>(
    body ? { body, method: 'POST', path, profile: desktopFsProfile() } : { path, profile: desktopFsProfile() }
  )
}

function gitGet<T>(route: string, params: Record<string, boolean | null | string | undefined>): Promise<T> {
  const query = new URLSearchParams()

  for (const [key, value] of Object.entries(params)) {
    if (value !== null && value !== undefined) {
      query.set(key, String(value))
    }
  }

  return desktopApi<T>(`/api/git/${route}?${query.toString()}`)
}

View on GitHub (pinned to c896c09c42)

Solutions

  1. Feature-gate git surfaces on the bridge: `if (!window.hermesDesktop) return disabledState`.
  2. Stub window.hermesDesktop = { api: vi.fn() } in unit tests of git-dependent components.
  3. Ensure git components are only mounted in desktop-only routes.
  4. Verify preload attachment if this fires inside Electron proper.

Example fix

// before
const status = await gitStatus(repoRoot)

// after
if (!window.hermesDesktop) {
  setGitUnavailable('Git tooling is available only in the Hermes desktop app')
  return
}
const status = await gitStatus(repoRoot)
Defensive patterns

Strategy: type-guard

Type guard

function hasDesktopBridge(w: Window): w is Window & { hermesDesktop: NonNullable<Window['hermesDesktop']> } {
  return Boolean((w as any).hermesDesktop)
}

Try / catch

if (!hasDesktopBridge(window)) { setGitUnavailable('Git tooling is desktop-only'); return }
try { const status = await gitStatus(repoRoot) } catch (e) { if (e instanceof Error && e.message === 'Hermes Desktop bridge is unavailable') setGitUnavailable() else throw e }

Prevention

When it happens

Trigger: Any desktop-git helper call (status, file-diff, branch ops, worktree lanes, review pane) executed in a browser/web build or jsdom test without a hermesDesktop stub; calling before preload injection completes.

Common situations: Git-dependent UI (coding rail, review pane) rendered in the web dashboard; tests for git facades without bridge mocks; debugging the renderer in an external browser.

Related errors


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