{"record":{"id":"544f2727b630c1d3","repo":"stablyai/orca","slug":"relay-credential-resolved-as-an-unexpected-credent","errorCode":null,"errorMessage":"relay credential resolved as an unexpected credential kind","messagePattern":"relay credential resolved as an unexpected credential kind","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mobile/src/transport/mobile-relay-e2ee-link.ts","lineNumber":124,"sourceCode":"  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) {\n      return\n    }\n    this.closed = true\n    this.channel.dispose()\n    this.options.onError(error)\n    this.socket.close()\n  }\n}\n\nfunction relaySocketUrl(endpoint: { cellUrl: string; relayHostId: string }): string {","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/src/transport/mobile-relay-e2ee-link.ts#L106-L142","documentation":"Thrown in MobileRelayE2eeLink.acceptHello when the hello succeeded (ok:true) but parsed.data.credentialKind ('invite'|'resume') does not equal this.options.expectedCredentialKind. The cell resolved the credential as a different kind than the client declared.","triggerScenarios":"The caller passed an invite token but set expectedCredentialKind:'resume', or passed a resume token while the option says 'invite'; the cell internally promoted an invite to resume after install completed.","commonSituations":"State-machine bug at the call site mislabeling the credential kind; pairing already completed on the host so the invite is no longer interpreted as invite; credential rotated between save and connect.","solutions":["Verify the credential string and expectedCredentialKind agree at the MobileRelayE2eeLink construction site.","If the host already installed the credential, switch to the resume flow (expectedCredentialKind:'resume' with the resume token).","Regenerate a credential of the correct kind from the host if the stored one was promoted."],"exampleFix":"// before\nnew MobileRelayE2eeLink({\n  credential: resumeToken,\n  expectedCredentialKind: 'invite', // wrong: token is a resume token\n  ...\n})\n// after\nnew MobileRelayE2eeLink({\n  credential: resumeToken,\n  expectedCredentialKind: 'resume',\n  ...\n})","handlingStrategy":"validation","validationCode":"function assertCredentialKind(credential: string, expectedCredentialKind: 'invite' | 'resume'): void {\n  // invite tokens and resume tokens are both 43-char base64url; the caller must\n  // track which one it holds. Assert the option matches the tracked provenance.\n  if (!credential) throw new Error('missing credential')\n  // expectedCredentialKind is the caller's source of truth, not derivable from the token\n}","typeGuard":null,"tryCatchPattern":"try {\n  const link = new MobileRelayE2eeLink(options)\n} catch (error) {\n  if (error instanceof Error && error.message === 'relay credential resolved as an unexpected credential kind') {\n    // re-evaluate whether the host already installed; switch to resume flow\n  }\n}","preventionTips":["Track credential provenance (invite vs resume) alongside the token string so expectedCredentialKind is always correct.","When the host reports install complete, flip the flow to resume and stop reusing the invite.","Set expectedCredentialKind explicitly at every construction site."],"tags":["relay","authentication","e2ee","state-machine"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}