NousResearch/hermes-agent · error

This Desktop build cannot refresh OAuth WebSocket tickets. U

Error message

This Desktop build cannot refresh OAuth WebSocket tickets. Update Hermes Desktop and try again.

What it means

Thrown by resolveGatewayWsUrl when the connection is in OAuth mode but the injected getGatewayWsUrl dependency (the ticket-minting bridge into the Electron host) is missing. OAuth-mode WebSocket URLs are short-lived tickets minted by the desktop main process; only it can refresh them, so a build or embedding without that bridge cannot reconnect.

Source

Thrown at apps/shared/src/websocket-url.ts:45

    super(message, options)
    this.name = 'GatewayReauthRequiredError'
  }
}

export function isGatewayReauthRequired(error: unknown): error is GatewayReauthRequiredError {
  return (
    error instanceof GatewayReauthRequiredError ||
    (typeof error === 'object' && error !== null && (error as { needsOauthLogin?: unknown }).needsOauthLogin === true)
  )
}

export async function resolveGatewayWsUrl(deps: ResolveGatewayWsUrlDeps, conn: GatewayWsConnection): Promise<string> {
  const mint = deps.getGatewayWsUrl
  const profile = conn.profile ?? null

  if (conn.authMode === 'oauth') {
    if (!mint) {
      throw new Error('This Desktop build cannot refresh OAuth WebSocket tickets. Update Hermes Desktop and try again.')
    }

    try {
      const result = await mint(profile)

      if (typeof result === 'string') {
        return result
      }

      if (result.ok) {
        return result.wsUrl
      }

      if (result.needsOauthLogin) {
        throw new GatewayReauthRequiredError(
          'Your remote gateway session has expired. Open Settings -> Gateway and click "Sign in" again.',
          { cause: new Error(result.error) }
        )

View on GitHub (pinned to c896c09c42)

Solutions

  1. Update Hermes Desktop so the main/preload process provides the gateway WS ticket API.
  2. If wiring the client manually, pass a getGatewayWsUrl dep that calls window.hermesDesktop's ticket endpoint whenever authMode is 'oauth'.
  3. In tests, inject a fake mint function instead of leaving it undefined.

Example fix

// before
resolveGatewayWsUrl({} as deps, conn) // oauth conn, no mint

// after
resolveGatewayWsUrl({ getGatewayWsUrl: (profile) => hermesDesktop.gateway.wsUrl(profile) }, conn)
Defensive patterns

Strategy: validation

Validate before calling

function canMintOAuthTickets(deps: ResolveGatewayWsUrlDeps): boolean {
  return typeof deps.getGatewayWsUrl === 'function'
}

// before constructing: if conn.authMode === 'oauth', require the mint dep

Type guard

function hasMintDep(deps: ResolveGatewayWsUrlDeps): deps is ResolveGatewayWsUrlDeps & {
  getGatewayWsUrl: (profile: null | string) => Promise<string | { ok: true; wsUrl: string } | { ok: false; needsOauthLogin?: boolean; error?: string }>
} {
  return typeof deps.getGatewayWsUrl === 'function'
}

Prevention

When it happens

Trigger: Constructing GatewayWsConnection with authMode 'oauth' but omitting deps.getGatewayWsUrl — typically in tests, the web dashboard (which uses a different auth path), or an older Desktop build whose preload lacks the ticket API.

Common situations: Version skew: an updated apps/shared library running inside a stale Electron binary that predates the OAuth ticket bridge; unit tests instantiating the shared client without the desktop dependency.

Related errors


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