stablyai/orca · error · EmulatorError

emulator_error

emulator_error

Error message

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

What it means

ensureAdbOk throws EmulatorError('emulator_error') when an AndroidCommandResult reports a non-zero exit code. The Android command runner deliberately resolves adb failures as data (code !== 0) rather than rejecting, so silent successes are impossible; callers opt into throwing by piping the result through ensureAdbOk with a human-readable label. The message embeds the trimmed stderr (falling back to stdout, then 'unknown error') for diagnosis.

Source

Thrown at src/main/emulator/android/android-adb-result.ts:8

import { EmulatorError } from '../emulator-errors'
import type { AndroidCommandResult } from './android-command-runner'

// AndroidCommandRunner resolves non-zero exits as data; callers must opt into
// throwing so adb failures do not become silent successful emulator actions.
export function ensureAdbOk(result: AndroidCommandResult, label: string): AndroidCommandResult {
  if (result.code !== 0) {
    throw new EmulatorError(
      'emulator_error',
      `${label} failed: ${(result.stderr || result.stdout).trim() || 'unknown error'}`
    )
  }
  return result
}

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Inspect the embedded stderr in the message — adb usually states the precise failure (device offline, unauthorized, more than one device).
  2. Verify the device is still present: `adb devices` and confirm the serial is 'device' (not 'offline'/'unauthorized').
  3. Restart the adb server (`adb kill-server && adb start-server`) when state is stale.
  4. On Linux permission errors, install the platform udev rules and ensure the user is in the plugdev/adb group.
  5. Re-run with the runner's logging enabled to capture the exact argv and full stderr if the trimmed message is incomplete.

Example fix

// before: treating adb result as always-OK
const r = await runner(sdk.adb, pushArgs(serial, jar))
use(r.stdout)
// after: opt into a hard failure with a label
import { ensureAdbOk } from '../android-adb-result'
const r = ensureAdbOk(await runner(sdk.adb, pushArgs(serial, jar)), 'scrcpy server push')
use(r.stdout)
Defensive patterns

Strategy: try-catch

Validate before calling

// Inspect the result before opting in; lets callers branch instead of throw.
import type { AndroidCommandResult } from './android-command-runner'
function adbOk(r: AndroidCommandResult): boolean {
  return r.code === 0
}

Type guard

import { EmulatorError } from '../emulator-errors'
function isEmulatorError(e: unknown): e is EmulatorError {
  return e instanceof EmulatorError
}

Try / catch

try {
  ensureAdbOk(await runner(sdk.adb, args), 'push')
} catch (e) {
  if (e instanceof EmulatorError && e.code === 'emulator_error') {
    // device may have disconnected — re-probe before retrying
    await refreshDeviceList()
  }
  throw e
}

Prevention

When it happens

Trigger: Calling ensureAdbOk(result, label) after any adb invocation routed through AndroidCommandRunner — e.g. scrcpy server push, scrcpy port forward, device probes — where result.code !== 0. Anywhere the runner is used and the caller needs a hard failure (contrast: boot polling where non-zero is an expected 'not booted yet' signal).

Common situations: Device disconnected between a serial resolution and the adb call (USB unplugged, emulator killed); insufficient adb permissions on Linux (missing udev rules) producing exit 1; an unauthorized device (adb in 'offline'/'unauthorized' state); a malformed serial or a stale adb server needing `adb killserver`; scrcpy server push failing because /sdcard is full or read-only.

Related errors


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