adb wait-for-device: Block Until the Emulator Is Ready
adb wait-for-device returns as soon as adb sees a device in the device state, which happens well before Android has finished booting.
The most common flaky mobile pipeline waits for the wrong signal. wait-for-device only proves the transport is up; you still have to poll sys.boot_completed before the OS is usable.
What it does
adb wait-for-device (and the transport-specific wait-for-usb-device / wait-for-local-device) blocks the shell until adb reports a matching device in the device state. It does not wait for the Android framework to finish starting, so apps and package manager calls can still fail right after it returns.
Common usage
adb wait-for-device
# then wait for the framework to actually finish booting
until [ "$(adb shell getprop sys.boot_completed | tr -d '\r')" = "1" ]; do
sleep 2
done
adb shell input keyevent 82 # dismiss the lock screenOptions
| Form | What it does |
|---|---|
| wait-for-device | Wait for any transport to reach the device state |
| wait-for-usb-device | Wait only for a USB-connected device |
| wait-for-local-device | Wait only for an emulator / local transport |
| getprop sys.boot_completed | Returns 1 once the framework has booted |
| getprop init.svc.bootanim | Returns stopped when the boot animation ends |
In CI
Wrap the poll in an outer timeout (for example timeout 300 bash -c ...) so a stuck emulator fails the job instead of hanging until the runner limit. The reactivecircus/android-emulator-runner action bakes this pattern in; if you launch the emulator yourself, replicate it.
Common errors in CI
A job that hangs forever at this step usually has no emulator starting at all; wait-for-device blocks indefinitely with no timeout. "device offline" right after it returns means you skipped the boot_completed poll. On cold boots, add extra sleep or use -no-snapshot-load so a corrupt snapshot does not leave the device stuck.