stablyai/orca · error

relay pairing client closed

Error message

relay pairing client closed

What it means

Thrown in the returned client's sendRequest when, after awaiting authenticatedPromise, closed || !authenticated is true. The socket has been torn down (via close(), a transport error, or onclose) or never authenticated, so no further RPCs can be sent.

Solutions

  1. Await the previous request and handle its rejection before sending again.
  2. Treat this error as terminal for the client; create a new one via connectMobileRelayForPairing to reconnect.
  3. Surface a disconnected state in the UI and disable further send attempts.

Example fix

// before
const resp = await client.sendRequest('install')
// ...later, after a close/error:
await client.sendRequest('resume-confirm') // throws 'relay pairing client closed'
// after
try {
  await client.sendRequest('install')
} catch {
  client = connectMobileRelayForPairing(args) // new client for retry
  await client.sendRequest('install')
}
Defensive patterns

Strategy: try-catch

Validate before calling

// The client does not expose `closed`; track it yourself alongside the handle.
let clientClosed = false
const client = connectMobileRelayForPairing(args)
const wrappedSend = async (method: string, params?: unknown) => {
  if (clientClosed) throw new Error('caller-side guard: client already closed')
  try {
    return await client.sendRequest(method, params)
  } catch (error) {
    clientClosed = true
    throw error
  }
}

Try / catch

try {
  await client.sendRequest('install')
} catch (error) {
  if (error instanceof Error && error.message === 'relay pairing client closed') {
    // terminal: build a new client via connectMobileRelayForPairing to retry
  }
}

Prevention

When it happens

Trigger: Calling sendRequest after close(), after a prior transport error/onclose triggered fail(), or while a previously awaited request was rejecting due to disconnect.

Common situations: The caller does not observe the rejection of a prior request and issues another; racing a request with a user-initiated close; a network drop mid-RPC.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/065b511778a17d37. Report an issue: GitHub.

Appendix: source

Thrown at mobile/src/transport/mobile-relay-physical-client.ts:166

      log('info', 'Relay: pairing socket closed', cellHost)
    } else {
      log('warn', 'Relay: pairing socket closed', pairingRelayErrorDetail(error))
    }
    channel.dispose()
    rejectAuthenticated(error)
    for (const request of pending.values()) {
      clearTimeout(request.timer)
      request.reject(error)
    }
    pending.clear()
    socket.close()
  }

  return {
    async sendRequest(method, params) {
      await authenticatedPromise
      if (closed || !authenticated) {
        throw new Error('relay pairing client closed')
      }
      const id = `relay-pair-${++requestCounter}`
      return new Promise<RpcResponse>((resolve, reject) => {
        const timer = setTimeout(() => {
          pending.delete(id)
          reject(new Error(`relay pairing RPC timed out: ${method}`))
        }, requestTimeoutMs)
        pending.set(id, { resolve, reject, timer })
        if (
          !channel.sendText(JSON.stringify({ id, deviceToken: args.deviceToken, method, params }))
        ) {
          clearTimeout(timer)
          pending.delete(id)
          reject(new Error('relay E2EE channel not ready'))
        }
      })
    },
    close: () => {

View on GitHub (pinned to 1136503c6a)