{"record":{"id":"d7e43a607bef9ca0","repo":"stablyai/orca","slug":"invalid-relay-hello-json","errorCode":null,"errorMessage":"invalid relay hello JSON","messagePattern":"invalid relay hello JSON","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mobile/src/transport/mobile-relay-e2ee-link.ts","lineNumber":114,"sourceCode":"          } else {\n            await this.channel.handleMessage(event.data)\n          }\n        })\n        .catch((error: unknown) => this.fail(asError(error)))\n    }\n    this.socket.onerror = () => this.fail(new Error('relay transport error'))\n    this.socket.onclose = (event) => this.fail(new RelayOuterError(event.code || 1006))\n  }\n\n  private acceptHello(raw: unknown): void {\n    if (typeof raw !== 'string') {\n      throw new Error('expected plaintext relay hello')\n    }\n    let value: unknown\n    try {\n      value = JSON.parse(raw)\n    } catch {\n      throw new Error('invalid relay hello JSON')\n    }\n    const parsed = RelayPhoneHelloSchema.safeParse(value)\n    if (!parsed.success) {\n      throw new Error('invalid relay hello')\n    }\n    if (!parsed.data.ok) {\n      throw new RelayOuterError(parsed.data.code)\n    }\n    if (parsed.data.credentialKind !== this.options.expectedCredentialKind) {\n      throw new Error('relay credential resolved as an unexpected credential kind')\n    }\n    this.outerReady = true\n    this.options.onHello?.(parsed.data)\n    this.channel.start()\n  }\n\n  private fail(error: Error): void {\n    if (this.closed) {","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/src/transport/mobile-relay-e2ee-link.ts#L96-L132","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const link = new MobileRelayE2eeLink(options)\n} catch (error) {\n  if (error instanceof Error && error.message === 'invalid relay hello JSON') {\n    // log redacted first frame, prompt reconnect; do not retry the same cell blindly\n  }\n}","preventionTips":["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."],"tags":["relay","websocket","json","e2ee","protocol"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}