stablyai/orca · error · EmulatorError

emulator_error

emulator_error

Error message

adb install failed: ${(result.stderr || result.stdout).trim() || 'unknown error'}

What it means

Thrown by installAndroidApk when `adb install` either exits non-zero OR prints a 'Failure'/'Error' marker to stdout/stderr. The dual check is deliberate: adb install is known to exit 0 while reporting 'Failure [...]' on stdout, so a code-only check would let failed installs pass as successes. The message embeds the trimmed adb output for diagnosis.

Source

Thrown at src/main/emulator/android/android-capability-operations.ts:25

import { logcatArgs, parseLogcatLine, type LogcatEntry } from './android-logcat'
import { parseUiAutomatorXml, type AndroidAxNode } from './uiautomator-tree'

// Impure capability operations: compose the pure arg-builders/parsers with the
// command runner. The backend exposes thin delegations to these.

const UIAUTOMATOR_DUMP_PATH = '/sdcard/window_dump.xml'

export async function installAndroidApk(
  runner: AndroidCommandRunner,
  sdk: AndroidSdkPaths,
  serial: string,
  apkPath: string,
  options?: { reinstall?: boolean }
): Promise<void> {
  const result = await runner(sdk.adb, installApkArgs(serial, apkPath, options))
  // adb install can exit 0 while printing "Failure [...]" to stdout.
  if (result.code !== 0 || /Failure|Error/i.test(`${result.stdout}${result.stderr}`)) {
    throw new EmulatorError(
      'emulator_error',
      `adb install failed: ${(result.stderr || result.stdout).trim() || 'unknown error'}`
    )
  }
}

export async function launchAndroidApp(
  runner: AndroidCommandRunner,
  sdk: AndroidSdkPaths,
  serial: string,
  packageName: string,
  activity?: string
): Promise<void> {
  ensureAdbOk(await runner(sdk.adb, launchAppArgs(serial, packageName, activity)), 'adb launch')
}

export async function setAndroidPermission(
  runner: AndroidCommandRunner,

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Read the embedded Failure code in the message — adb's reason string (e.g. INSTALL_FAILED_UPDATE_INCOMPATIBLE) names the exact problem.
  2. For signature mismatches, uninstall first: `adb -s <serial> uninstall <package>` then reinstall, or pass { reinstall: true } which maps to adb install -r.
  3. For ABI mismatches, build a universal APK or include the emulator's ABI (x86_64 for standard emulators).
  4. Free up emulator storage (`adb shell df /data`) and clear unused packages if INSUFFICIENT_STORAGE appears.
  5. For downgrades, build a higher versionCode or use adb install -d (downgrade) explicitly.

Example fix

// before: reinstall collides with prior signature
await installAndroidApk(runner, sdk, serial, apkPath)
// after: permit replace of existing install
await installAndroidApk(runner, sdk, serial, apkPath, { reinstall: true })
Defensive patterns

Strategy: validation

Validate before calling

// Pre-check signature/version compatibility before install.
async function canReinstall(runner, sdk, serial, pkg): Promise<boolean> {
  const r = await runner(sdk.adb, ['-s', serial, 'shell', 'pm', 'path', pkg])
  return r.code === 0 && r.stdout.includes('package:')
}

Type guard

import { EmulatorError } from '../emulator-errors'
function isInstallError(e: unknown): e is EmulatorError {
  return e instanceof EmulatorError && e.code === 'emulator_error' && /install/i.test(e.message)
}

Try / catch

try {
  await installAndroidApk(runner, sdk, serial, apk, { reinstall: true })
} catch (e) {
  if (e instanceof EmulatorError && /UPDATE_INCOMPATIBLE/.test(e.message)) {
    await runner(sdk.adb, ['-s', serial, 'uninstall', pkg])
    await installAndroidApk(runner, sdk, serial, apk)
    return
  }
  throw e
}

Prevention

When it happens

Trigger: installAndroidApk(runner, sdk, serial, apkPath, options) where the install result has code !== 0, or where /Failure|Error/i matches the concatenation of stdout and stderr. Commonly triggered by an APK signed with an incompatible signature, an already-installed package with reinstall not set, an APK missing a required ABI, or insufficient storage.

Common situations: Reinstalling an APK signed differently without --reinstall (INSTALL_FAILED_UPDATE_INCOMPATIBLE / signatures do not match); INSTALL_FAILED_NO_MATCHING_ABI on a missing architecture; INSUFFICIENT_STORAGE on a full emulator; downgrade attempt (INSTALL_FAILED_VERSION_DOWNGRADE); an APK built for a different minSdk than the emulator's API level.

Related errors


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