stablyai/orca · error

expected plaintext relay hello

Error message

expected plaintext relay hello

What it means

Thrown in connectMobileRelayForPairing's acceptRelayHello when the first WebSocket message (raw) is not a string. The relay cell must send a plaintext text frame as the hello; a binary frame at this stage is a protocol violation.

Source

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

    inboundChain = inboundChain
      .then(async () => {
        if (closed) {
          return
        }
        if (!outerReady) {
          acceptRelayHello(event.data)
          return
        }
        await channel.handleMessage(event.data)
      })
      .catch((error: unknown) => fail(asError(error)))
  }
  socket.onerror = () => fail(new Error('relay transport error'))
  socket.onclose = (event) => fail(new RelayOuterError(event.code || 1006))

  function acceptRelayHello(raw: unknown): void {
    if (typeof raw !== 'string') {
      throw new Error('expected plaintext relay hello')
    }
    let value: unknown
    try {
      value = JSON.parse(raw)
    } catch {
      throw new Error('invalid relay hello JSON')
    }
    const parsed = RelayPhoneHelloSchema.safeParse(value)
    if (!parsed.success) {
      throw new Error('invalid relay hello')
    }
    if (!parsed.data.ok) {
      throw new RelayOuterError(parsed.data.code)
    }
    if (parsed.data.credentialKind !== (args.expectedCredentialKind ?? 'invite')) {
      throw new Error('relay credential resolved as an unexpected credential kind')
    }
    outerReady = true

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Verify the relay cell sends a text hello frame per the protocol.
  2. Check the cell build/version against the client expectation.
  3. In tests, ensure createSocket mocks emit a string for the first frame.
Defensive patterns

Strategy: try-catch

Type guard

function isTextFrame(raw: unknown): raw is string {
  return typeof raw === 'string'
}

Try / catch

try {
  const client = connectMobileRelayForPairing(args)
} catch (error) {
  if (error instanceof Error && error.message === 'expected plaintext relay hello') {
    // cell sent a binary first frame; verify cell version, do not retry unchanged
  }
}

Prevention

When it happens

Trigger: The cell sent an ArrayBuffer/Blob as the first frame instead of a text frame, or a misconfigured cell opened the handshake with binary.

Common situations: A cell version that leads with a binary frame; a middlebox converting text frames to binary; a test createSocket mock sending the wrong type for the first message.

Related errors


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