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
- Catch MobileRelayUpgradeHostRemovedError specifically and abort the upgrade silently (the user's remove intent is authoritative).
- Do NOT retry the upgrade — it would resurrect a deliberately-removed host.
- Surface 'Host removed — upgrade cancelled' as an informational toast, not an error.
- 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
- Catch MobileRelayUpgradeHostRemovedError specifically — it is intentional, not a bug.
- Do NOT retry the upgrade after this error; it would resurrect a removed host.
- Re-check host existence right before saveExistingHostRelayUpgrade to avoid the race.
- Cancel any in-flight upgrade when the user initiates a host removal.
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
- Host not found
- Terminal input is locked by another client.
- mobile relay pairing recovery pending
- relay pairing client closed
- Refusing to patch node-pty ${packageJson.version}; expected
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/7e8546d2db631d8e.
Report an issue: GitHub.