{"record":{"id":"1f611b18793b465b","repo":"stablyai/orca","slug":"e2ee-device-authentication-rejected","errorCode":null,"errorMessage":"E2EE device authentication rejected","messagePattern":"E2EE device authentication rejected","errorType":"exception","errorClass":"MobileE2EEAuthenticationError","httpStatus":null,"severity":"critical","filePath":"mobile/src/transport/mobile-e2ee-v2-physical-channel.ts","lineNumber":108,"sourceCode":"  private async processMessage(raw: unknown, generation: number): Promise<void> {\n    if (generation !== this.generation) {\n      return\n    }\n    if (this.state === 'awaiting-ready') {\n      this.acceptReady(raw)\n      return\n    }\n\n    const plaintext =\n      typeof raw === 'string'\n        ? this.args.session.openText(raw)\n        : await this.openBinary(raw, generation)\n    if (generation !== this.generation || plaintext === null) {\n      return\n    }\n    if (this.state === 'awaiting-authenticated') {\n      if (typeof plaintext === 'string' && isAuthenticationRejection(plaintext)) {\n        throw new MobileE2EEAuthenticationError()\n      }\n      if (typeof plaintext !== 'string' || !this.isAuthenticated(plaintext)) {\n        throw new Error('Invalid E2EE v2 authenticated response')\n      }\n      this.state = 'ready'\n      this.args.onAuthenticated()\n    } else if (typeof plaintext === 'string') {\n      this.args.onText(plaintext)\n    } else {\n      this.args.onBinary(plaintext)\n    }\n  }\n\n  private acceptReady(raw: unknown): void {\n    if (typeof raw !== 'string') {\n      throw new Error('Expected plaintext E2EE v2 ready')\n    }\n    let ready: unknown","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/mobile/src/transport/mobile-e2ee-v2-physical-channel.ts#L90-L126","documentation":"`MobileE2EEAuthenticationError` is thrown by the physical channel when, in the `awaiting-authenticated` state, the decrypted plaintext is a JSON message with `type === 'e2ee_error'` (detected by `isAuthenticationRejection`). That is the desktop's explicit signal that the mobile device's `e2ee_auth` payload — device token or transcript hash — was rejected. It is a distinct, named error class so callers can branch on it rather than string-matching.","triggerScenarios":"Desktop replied to `e2ee_auth` with `{ \"type\": \"e2ee_error\", ... }`; the device token presented in `e2ee_auth` is unknown, expired, or revoked on the desktop side; the transcript hash did not match the desktop's view of the handshake transcript.","commonSituations":"A host that was unpaired or reset while the mobile still held a stale device token; desktop-side rollback to a state that does not recognize the device; user revoked the device from the desktop UI between pairing and reconnect.","solutions":["Catch `MobileE2EEAuthenticationError` specifically in the transport error handler and surface a re-pair flow rather than retrying.","If the device token may be stale, clear the stored credential bundle and re-run pairing.","Verify the desktop is running a protocol-compatible version (v2 e2ee) before retry."],"exampleFix":"// before\ntry { link.sendText('hello') } catch (e) { retry() } // wrong: auth errors are not retried\n\n// after\ntry {\n  // ...\n} catch (e) {\n  if (e instanceof MobileE2EEAuthenticationError) {\n    await clearDeviceTokenAndStartRepair(hostId)\n  } else {\n    throw e\n  }\n}","handlingStrategy":"try-catch","validationCode":"// No pre-call validation: the rejection is the desktop's response. Detect after the fact.\n// Ensure onError handler branches on the error class.","typeGuard":"function isAuthRejection(e: unknown): e is MobileE2EEAuthenticationError { return e instanceof MobileE2EEAuthenticationError }","tryCatchPattern":"channel.onError = (e) => { if (e instanceof MobileE2EEAuthenticationError) { startRepairFlow(hostId) } else { reportTransient(e) } }","preventionTips":["Always check `instanceof MobileE2EEAuthenticationError` in the error handler.","On this error, clear the device token and re-pair rather than retrying.","Keep device tokens fresh — detect server-side revocation via resume failures."],"tags":["authentication","e2ee","security","relay"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}