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
- Inspect the embedded stderr in the message — adb usually states the precise failure (device offline, unauthorized, more than one device).
- Verify the device is still present: `adb devices` and confirm the serial is 'device' (not 'offline'/'unauthorized').
- Restart the adb server (`adb kill-server && adb start-server`) when state is stale.
- On Linux permission errors, install the platform udev rules and ensure the user is in the plugdev/adb group.
- 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
- Validate the device serial is 'device' status in adb devices before issuing commands.
- Restart the adb server (`adb kill-server && adb start-server`) when stale state is suspected.
- On Linux, install platform udev rules so adb exits cleanly.
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.