stablyai/orca · error

invalid relay hello JSON

Error message

invalid relay hello JSON

What it means

Thrown in MobileRelayE2eeLink.acceptHello when JSON.parse(raw) throws on the relay cell's first WebSocket frame. The cell must send a plaintext JSON 'relay-hello' as the very first message after the client's relay-auth frame; this fires when that text frame is not valid JSON.

Source

Thrown at mobile/src/transport/mobile-relay-e2ee-link.ts:114

          } else {
            await this.channel.handleMessage(event.data)
          }
        })
        .catch((error: unknown) => this.fail(asError(error)))
    }
    this.socket.onerror = () => this.fail(new Error('relay transport error'))
    this.socket.onclose = (event) => this.fail(new RelayOuterError(event.code || 1006))
  }

  private acceptHello(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 !== this.options.expectedCredentialKind) {
      throw new Error('relay credential resolved as an unexpected credential kind')
    }
    this.outerReady = true
    this.options.onHello?.(parsed.data)
    this.channel.start()
  }

  private fail(error: Error): void {
    if (this.closed) {

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Log the redacted raw first frame to identify what the cell actually sent (it is plaintext, but redact any token-looking substrings).
  2. Verify endpoint.cellUrl resolves to a real relay cell and not an error/landing page.
  3. Reconnect over a clean network to rule out a captive portal or middlebox.
  4. Confirm the relay cell build matches the relay-hello protocol version the client expects.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const link = new MobileRelayE2eeLink(options)
} catch (error) {
  if (error instanceof Error && error.message === 'invalid relay hello JSON') {
    // log redacted first frame, prompt reconnect; do not retry the same cell blindly
  }
}

Prevention

When it happens

Trigger: The first onmessage event (before outerReady) delivers a string that JSON.parse rejects: an HTML error page, a plain-text banner, a truncated frame, or a proxy/captive-portal interstitial injected into the WS stream.

Common situations: A captive portal or TLS-terminating middlebox rewriting the WS frame; cellUrl actually pointing at a load-balancer error page; a relay cell version that emits a non-JSON banner before the hello; a flaky network delivering a partial first frame.

Related errors


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