stablyai/orca · error · EmulatorError

emulator_unsupported

emulator_unsupported

Error message

${capability} is not supported by the ${backend.kind} emulator backend

What it means

runCapability rejects when the resolved backend's capabilities map marks the requested verb false. The iOS backend disables install/launch/permissions/logcat (only accessibilityTree is true); the Android backend enables all five. This turns an impossible verb into a clear error instead of a silent no-op.

Source

Thrown at src/main/emulator/emulator-bridge.ts:238

        )
      }
      // Heal sessions registered without an axUrl (parse-time derivation only
      // covers fresh --detach output) by deriving it from the mjpeg stream URL.
      const axUrl = session?.axUrl ?? deriveAxUrlFromStreamUrl(session?.streamUrl)
      return backend.accessibilityTree!(udid, axUrl)
    })
  }

  // Runs a capability-gated verb against the resolved target, rejecting backends
  // that do not advertise the capability (e.g. install/logcat on iOS).
  async runCapability<T>(
    capability: keyof EmulatorBackendCapabilities,
    opts: EmulatorTargetOpts | undefined,
    run: (backend: EmulatorBackend, deviceId: string) => Promise<T>
  ): Promise<T> {
    const { backend, device } = await this.resolveTarget(opts)
    if (!backend.capabilities[capability]) {
      throw new EmulatorError(
        'emulator_unsupported',
        `${capability} is not supported by the ${backend.kind} emulator backend`
      )
    }
    return run(backend, device)
  }

  async startHelperForDevice(device: string): Promise<EmulatorSessionInfo> {
    const backend = await this.backendForDevice(device)
    return backend.startSession(device)
  }

  async kill(device?: string, worktreeId?: string): Promise<string> {
    const { backend, udid } = await this.resolveStopTarget(device, worktreeId)
    await backend.stopHelperForDevice(udid, {
      helperPid: this.sessionRegistry.getSession(udid)?.pid,
      includeOrphaned: true
    })

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Target an Android emulator/device (serial or AVD name) for install/logcat/launch/permissions.
  2. Check `backend.capabilities[capability]` before invoking the verb and skip/warn on unsupported backends.
  3. Pass an explicit `--device <android-serial>` so resolveTarget routes to the Android backend.

Example fix

// before
await bridge.runCapability('logcat', { device: iosUdid }, run) // throws emulator_unsupported

// after
const { backend } = await bridge.resolveTarget({ device })
if (backend.capabilities.logcat) {
  await bridge.runCapability('logcat', { device }, run)
}
Defensive patterns

Strategy: validation

Validate before calling

const { backend } = await bridge.resolveTarget(opts)
if (!backend.capabilities[capability]) { /* skip or route to android */ }

Type guard

function isEmulatorError(e: unknown, code = 'emulator_unsupported'): e is import('./emulator-errors').EmulatorError {
  return e instanceof Error && (e as any).code === code && e.name === 'EmulatorError'
}

Try / catch

try { await bridge.runCapability('logcat', opts, run) }
catch (e) { if (isEmulatorError(e, 'emulator_unsupported')) { /* pick an android device */ } else throw e }

Prevention

When it happens

Trigger: bridge routes installApp/launchApp/setPermission/logcat (or accessibilityTree) through runCapability; backend.capabilities[capability] is false at emulator-bridge.ts:237. Concrete case: calling install/logcat when backend.kind === 'ios'.

Common situations: Running an Android-only command (logcat, install, permissions) against an iOS simulator; a device selector matched the iOS backend when an Android serial was intended; a recipe/script assumes the full Android capability set on every backend.

Related errors


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