{"record":{"id":"53d47f818f3333eb","repo":"stablyai/orca","slug":"relay-pairing-rpc-unavailable-after-relay-path-aut","errorCode":null,"errorMessage":"relay pairing RPC unavailable after relay path authentication","messagePattern":"relay pairing RPC unavailable after relay path authentication","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mobile/src/transport/pre-profile-pairing-coordinator.ts","lineNumber":223,"sourceCode":"    return { hostId }\n  }\n\n  journal = {\n    ...journal,\n    metadata: {\n      ...journal.metadata,\n      winner: winner.path,\n      authorizationMode: winner.path === 'direct' ? 'authenticated-direct' : 'relay-basis'\n    }\n  }\n  await dependencies.updateJournal(journal.metadata.journalId, () => journal!.metadata)\n  const provision = await winner.client.sendRequest('pairing.provisionRelay', {\n    reqId: journal.metadata.installReqId,\n    newResumeTokenHash: journal.metadata.pendingResumeTokenHash\n  })\n  if (isMethodNotFound(provision)) {\n    if (winner.path !== 'direct') {\n      throw new Error('relay pairing RPC unavailable after relay path authentication')\n    }\n    await dependencies.saveHost(baseHost(offer, hostId, hostName, now))\n    await dependencies.clearJournal(journal.metadata.journalId)\n    return { hostId }\n  }\n  const installed = DeviceCredentialInstalledSchema.parse(requireSuccess(provision))\n  const endpoints = PairingGetEndpointsResultSchema.parse(\n    requireSuccess(\n      await winner.client.sendRequest('pairing.getEndpoints', {\n        installReqId: journal.metadata.installReqId\n      })\n    )\n  )\n  assertCommittedInstall(endpoints.installStatus, installed)\n  if (!endpoints.relay) {\n    throw new Error('desktop returned no relay endpoint after credential install')\n  }\n  assertActive(isDisposed)","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/src/transport/pre-profile-pairing-coordinator.ts#L205-L241","documentation":"Thrown in runPairing after winner.client.sendRequest('pairing.provisionRelay') returned a JSON-RPC method_not_found error AND the winning candidate path was 'relay' (not 'direct'). The coordinator tolerates a missing provisionRelay on the direct path (line 224-227, for older desktops that predate relay support), but a relay-path winner that lacks provisionRelay is unrecoverable: the relay was authenticated yet the desktop cannot mint the durable relay credential, so the pairing cannot proceed to getEndpoints.","triggerScenarios":"A relay-path winner (racePairingCandidates returned { path: 'relay', client }) dialing a desktop whose RPC surface does not implement 'pairing.provisionRelay' — i.e., an older Orca desktop version that happens to still accept relay transport but lacks the credential-install RPC. isMethodNotFound(provision) is true AND winner.path === 'relay'.","commonSituations":"Version skew: mobile client newer than the desktop, where the desktop exposes relay transport but predates the provisionRelay RPC. Rare in normal operation because relay transport and provisionRelay shipped together; appears in partial upgrades, custom desktop builds, or against a mock/test desktop that implements some pairing RPCs but not provisionRelay.","solutions":["Upgrade the desktop Orca runtime to a version that implements 'pairing.provisionRelay' (it must ship alongside relay transport support).","If a legacy desktop must be supported, force the direct path by ensuring the relay candidate loses the race (e.g., withhold offer.relay) so winner.path === 'direct' takes the tolerant branch at line 224.","Verify the desktop's RPC method registry exposes 'pairing.provisionRelay' before advertising relay capability to mobile clients."],"exampleFix":"// before — relay-capable offer sent to a desktop lacking provisionRelay\nconst offer = { ...base, relay: relayEndpoint }\n\n// after — direct-only until desktop is upgraded\nconst offer = { ...base /* no relay */ }","handlingStrategy":"validation","validationCode":"// Before advertising relay, confirm the desktop implements provisionRelay\nasync function desktopSupportsProvisionRelay(client: PairingCandidateClient): Promise<boolean> {\n  const probe = await client.sendRequest('rpc.methods', {})\n  if (!probe.ok) return false\n  const methods = (probe.result as { methods?: string[] }).methods ?? []\n  return methods.includes('pairing.provisionRelay')\n}","typeGuard":"function isRelayProvisionUnavailable(error: unknown): boolean {\n  return error instanceof Error\n    && error.message === 'relay pairing RPC unavailable after relay path authentication'\n}","tryCatchPattern":"try {\n  await runPairing(...)\n} catch (error) {\n  if (isRelayProvisionUnavailable(error)) {\n    // retry without relay so the direct path takes the tolerant branch\n    return startPreProfilePairing({ ...args, offer: { ...args.offer, relay: undefined } })\n  }\n  throw error\n}","preventionTips":["Keep mobile and desktop Orca versions aligned so relay transport and provisionRelay ship together.","Capability-probe the desktop's RPC method registry before offering relay to mobile clients.","When supporting legacy desktops, omit offer.relay so the direct-only branch is taken."],"tags":["pairing","relay","rpc","version-skew","method-not-found"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}