stablyai/orca · info

mobile pairing cancelled

Error message

mobile pairing cancelled

What it means

Thrown by assertActive(isDisposed) (line 312-316) when the pairing flow was disposed between async steps. dispose() (line 80-93) sets disposed=true, clears the timer, and closes all candidates; assertActive is called at four checkpoints in runPairing (after resolveHostIdentity, after saveJournal, after racePairingCandidates, and before writeCredentialBundle) to abort promptly if the user cancelled or the timeout fired. Unlike error 482, this fires when the disposal was observed synchronously at a checkpoint rather than manifesting as a timeout-masked rejection.

Source

Thrown at mobile/src/transport/pre-profile-pairing-coordinator.ts:314

function assertCommittedInstall(
  status:
    | { state: 'not-found' }
    | { state: 'committed'; result: DeviceCredentialInstalled }
    | undefined,
  installed: DeviceCredentialInstalled
): void {
  if (
    !status ||
    status.state !== 'committed' ||
    JSON.stringify(status.result) !== JSON.stringify(installed)
  ) {
    throw new Error('relay credential install was not authoritatively reconciled')
  }
}

function assertActive(isDisposed: () => boolean): void {
  if (isDisposed()) {
    throw new Error('mobile pairing cancelled')
  }
}

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Treat as user-initiated cancellation — no recovery action needed; surface 'Pairing cancelled' in the UI.
  2. If unexpected, audit callers of attempt.dispose() to confirm none fire spuriously (e.g., a React effect cleanup that runs on every render).
  3. Ensure only one startPreProfilePairing attempt is live at a time; dispose the previous before starting a new one.
  4. In tests, await attempt.result (which will reject with this message) before asserting downstream state.
Defensive patterns

Strategy: try-catch

Type guard

function isPairingCancelledError(error: unknown): boolean {
  return error instanceof Error && error.message === 'mobile pairing cancelled'
}

Try / catch

try {
  const { hostId } = await attempt.result
} catch (error) {
  if (isPairingCancelledError(error)) {
    // user-initiated cancellation — no recovery
    return
  }
  throw error
}

Prevention

When it happens

Trigger: PreProfilePairingAttempt.dispose() was called (user cancelled pairing UI, app backgrounded with single-pairing-attempt policy, or a newer pairing offer superseded this one) while runPairing was awaiting an async step. On resumption of the next checkpoint, isDisposed() returns true and assertActive throws.

Common situations: User taps 'Cancel' on the pairing screen mid-handshake; a second scan of a pairing QR code supersedes the first; the app is killed/backgrounded and the foreground handler disposes the in-flight attempt. Expected behavior, not a bug.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/fe55cb6808cbbef5. Report an issue: GitHub.