{"record":{"id":"af78c4712c6cf9ab","repo":"stablyai/orca","slug":"expected-plaintext-relay-hello-af78c4","errorCode":null,"errorMessage":"expected plaintext relay hello","messagePattern":"expected plaintext relay hello","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mobile/src/transport/mobile-relay-physical-client.ts","lineNumber":119,"sourceCode":"    inboundChain = inboundChain\n      .then(async () => {\n        if (closed) {\n          return\n        }\n        if (!outerReady) {\n          acceptRelayHello(event.data)\n          return\n        }\n        await channel.handleMessage(event.data)\n      })\n      .catch((error: unknown) => fail(asError(error)))\n  }\n  socket.onerror = () => fail(new Error('relay transport error'))\n  socket.onclose = (event) => fail(new RelayOuterError(event.code || 1006))\n\n  function acceptRelayHello(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 !== (args.expectedCredentialKind ?? 'invite')) {\n      throw new Error('relay credential resolved as an unexpected credential kind')\n    }\n    outerReady = true","sourceCodeStart":101,"sourceCodeEnd":137,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/src/transport/mobile-relay-physical-client.ts#L101-L137","documentation":"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.","triggerScenarios":"The cell sent an ArrayBuffer/Blob as the first frame instead of a text frame, or a misconfigured cell opened the handshake with binary.","commonSituations":"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.","solutions":["Verify the relay cell sends a text hello frame per the protocol.","Check the cell build/version against the client expectation.","In tests, ensure createSocket mocks emit a string for the first frame."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isTextFrame(raw: unknown): raw is string {\n  return typeof raw === 'string'\n}","tryCatchPattern":"try {\n  const client = connectMobileRelayForPairing(args)\n} catch (error) {\n  if (error instanceof Error && error.message === 'expected plaintext relay hello') {\n    // cell sent a binary first frame; verify cell version, do not retry unchanged\n  }\n}","preventionTips":["Confirm the relay cell emits a text hello frame per the protocol before relying on it.","In tests, have createSocket mocks send a string as the first frame.","Treat a binary first frame as a cell-side defect, not a retryable transient."],"tags":["relay","websocket","pairing","protocol"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}