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
- Log the redacted raw first frame to identify what the cell actually sent (it is plaintext, but redact any token-looking substrings).
- Verify endpoint.cellUrl resolves to a real relay cell and not an error/landing page.
- Reconnect over a clean network to rule out a captive portal or middlebox.
- 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
- Validate endpoint.cellUrl is an https origin before constructing the link.
- Surfacing onError to the user with a reconnect action rather than silent retry.
- In tests, have createSocket mocks emit a valid JSON relay-hello string as the first frame.
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
- invalid relay hello
- relay_outer_${code}
- expected plaintext relay hello
- invalid relay hello JSON
- E2EE device authentication rejected
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/d7e43a607bef9ca0.
Report an issue: GitHub.