stablyai/orca · warning · MobileRelayUpgradeHostRemovedError

mobile relay upgrade host was removed

Error message

mobile relay upgrade host was removed

What it means

persistHost was called with requireExisting=true (via saveExistingHostRelayUpgrade) but the host ID was not found in the stored list. The code throws MobileRelayUpgradeHostRemovedError specifically to prevent an in-flight relay upgrade from silently resurrecting a host the user already removed between when the upgrade started and when it committed.

Source

Thrown at mobile/src/transport/host-store.ts:193

  let tokenCommittedBeforeMetadata = false
  try {
    await mutateStoredHosts(async (hosts) => {
      const index = hosts.findIndex((h) => h.id === stored.id)
      for (const candidate of hosts) {
        if (candidate.id !== stored.id && candidate.publicKeyB64 === stored.publicKeyB64) {
          duplicateHostIds.add(candidate.id)
        }
      }
      let next: StoredHostProfile[]
      if (index !== -1) {
        updatedExistingHost = true
        // Why: an authoritative save is the safe point to collapse pre-existing duplicate rows to the preserved host id.
        next = hosts
          .filter(({ id }) => !duplicateHostIds.has(id))
          .map((candidate) => (candidate.id === stored.id ? stored : candidate))
      } else if (requireExisting) {
        // Why: an in-flight relay upgrade must not resurrect a host the user removed.
        throw new MobileRelayUpgradeHostRemovedError('mobile relay upgrade host was removed')
      } else {
        next = [...hosts.filter(({ id }) => !duplicateHostIds.has(id)), stored]
      }
      if (duplicateHostIds.size > 0) {
        if (index === -1) {
          // Why: process death between the early token write and metadata publication must leave cleanup discoverable.
          await recordHostCredentialCleanupIntent(stored.id)
          cleanupIntentRecordedBeforeMetadata = true
        }
        for (const duplicateHostId of duplicateHostIds) {
          await recordHostCredentialCleanupIntent(duplicateHostId)
        }
        // Why: never remove the only usable same-key row until its replacement credential is durable.
        await commitDeviceToken(stored.id, validated.deviceToken)
        tokenCommittedBeforeMetadata = true
      }
      return next
    })

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Catch MobileRelayUpgradeHostRemovedError specifically and abort the upgrade silently (the user's remove intent is authoritative).
  2. Do NOT retry the upgrade — it would resurrect a deliberately-removed host.
  3. Surface 'Host removed — upgrade cancelled' as an informational toast, not an error.
  4. Guard the upgrade entry point by re-checking the host exists immediately before saveExistingHostRelayUpgrade.

Example fix

// before
try {
  await saveExistingHostRelayUpgrade(host)
} catch (e) {
  showError(e.message)
}

// after — the removal is intentional, abort quietly
try {
  await saveExistingHostRelayUpgrade(host)
} catch (e) {
  if (e instanceof MobileRelayUpgradeHostRemovedError) {
    navigateAway() // user already removed it
    return
  }
  throw e
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Re-check the host exists immediately before a relay upgrade
const hosts = await loadHosts()
if (!hosts.some((h) => h.id === host.id)) {
  return // host already removed
}

Type guard

import { MobileRelayUpgradeHostRemovedError } from './host-store'

function isRelayHostRemoved(e: unknown): e is MobileRelayUpgradeHostRemovedError {
  return e instanceof MobileRelayUpgradeHostRemovedError
}

Try / catch

try {
  await saveExistingHostRelayUpgrade(host)
} catch (e) {
  if (e instanceof MobileRelayUpgradeHostRemovedError) {
    // User removed the host — abort the upgrade silently
    return
  }
  throw e
}

Prevention

When it happens

Trigger: User started a relay-direct upgrade, then removed the host from Settings before the upgrade's metadata write landed; a duplicate-key collapse removed the row; a concurrent removeHost won the mutation-chain race.

Common situations: User taps 'Upgrade to direct connection' then quickly swipes to delete the host; relay upgrade retries after the host was already removed; rapid pair/unpair actions racing the upgrade flow.

Related errors


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