Summary
agent-device settings airplane on does not take an Android device offline. It writes airplane_mode_on=1 and then sends an android.intent.action.AIRPLANE_MODE broadcast, which requires a system permission and fails. Because the settings put runs first and succeeds, the device is left reporting airplane mode while connectivity is fully up.
This fails dirty rather than cleanly: the CLI surfaces an error, but the device state has already been half-mutated, so anything that checks airplane_mode_on sees 1 while traffic still flows.
Repro
Android emulator, ro.build.version.release=17, ro.build.version.sdk=37:
$ agent-device settings airplane on --platform android
Error (COMMAND_FAILED): Exception occurred while executing 'broadcast': Hint: Retry with --debug and inspect diagnostics log for details.
# diagnostics ndjson
{"level":"error","phase":"platform_command","command":"settings","data":{"error":"adb exited with code 255"}}
$ adb shell settings get global airplane_mode_on 1 # <- setting landed
$ adb shell ping -c 2 8.8.8.8
2 packets transmitted, 2 received, 0% packet loss # <- still online
Expected: the device is offline, or the command fails without changing device state.
Actual: the command fails, airplane_mode_on=1, and the network is unaffected.
Why this happens in agent-device
dist/src/android.js, the settings handler's airplane case:
case `airplane`: {
let t = A(r), n = t ? `1` : `0`, i = t ? `true` : `false`;
await d(e, [`shell`, `settings`, `put`, `global`, `airplane_mode_on`, n]),
await d(e, [`shell`, `am`, `broadcast`, `-a`,
`android.intent.action.AIRPLANE_MODE`, `--ez`, `state`, i]);
return
}
settings put global airplane_mode_on <0|1> succeeds — it only writes the setting.
am broadcast -a android.intent.action.AIRPLANE_MODE is rejected; broadcasting that action is restricted to system callers, so adb exits 255.
- Nothing ever tells the connectivity stack to act, so the radio state never changes.
Two notes:
- Not a regression. I diffed the published
0.20.0 and 0.20.10 tarballs and this block is identical (modulo minified identifier renaming), so upgrading does not help.
- The
wifi case has a related gap. settings wifi off runs svc wifi disable, which genuinely disables Wi-Fi, but an emulator then falls back to cellular and stays online — so it also cannot be used to reach an offline state.
Suggested fix
cmd connectivity airplane-mode <enable|disable> works on Android 11+, needs no root, and actually drives the connectivity stack. Verified on the same API 37 emulator:
$ adb shell cmd connectivity airplane-mode enable
$ adb shell cmd connectivity airplane-mode
enabled
$ adb shell ping -c 2 8.8.8.8
connect: Network is unreachable
Round trip back to disable restores connectivity cleanly. Latency from issuing the command to traffic actually failing was measured at 65–365ms across trials.
Suggested shape: try cmd connectivity airplane-mode first, and fall back to the existing settings put + broadcast pair only if that command is unavailable, so old devices keep whatever behaviour they have today. If the fallback is kept, it would be worth ordering the broadcast before the settings put, or reverting the setting on broadcast failure, so a failure cannot leave airplane_mode_on inconsistent with actual connectivity.
Related: no setAirplaneMode in the Maestro dialect
Possibly a separate issue, but it is the same "Maestro parity" shape as #2159.
|
Real Maestro |
agent-device --maestro |
setAirplaneMode |
supported (Android) |
not implemented |
toggleAirplaneMode |
supported (Android) |
not implemented |
Maestro documents both commands as Android-supported. In --maestro mode, neither is recognized, and the runScript sandbox exposes only output, json, and http.post — no require/process — so a flow cannot reach adb by any other route either.
The practical effect is that a flow cannot change connectivity mid-run at all: offline coverage has to be split into separate flows driven by an external script, or routed through a host-side HTTP sidecar called via http.post. Since settings airplane already exists internally, wiring those two Maestro commands to it looks like a small addition once the underlying toggle works.
Environment
agent-device 0.20.0 and 0.20.10 (same behaviour on both)
- Android emulator, Pixel 10,
ro.build.version.release=17 / sdk=37
- macOS host
Summary
agent-device settings airplane ondoes not take an Android device offline. It writesairplane_mode_on=1and then sends anandroid.intent.action.AIRPLANE_MODEbroadcast, which requires a system permission and fails. Because thesettings putruns first and succeeds, the device is left reporting airplane mode while connectivity is fully up.This fails dirty rather than cleanly: the CLI surfaces an error, but the device state has already been half-mutated, so anything that checks
airplane_mode_onsees1while traffic still flows.Repro
Android emulator,
ro.build.version.release=17,ro.build.version.sdk=37:Expected: the device is offline, or the command fails without changing device state.
Actual: the command fails,
airplane_mode_on=1, and the network is unaffected.Why this happens in agent-device
dist/src/android.js, thesettingshandler'sairplanecase:settings put global airplane_mode_on <0|1>succeeds — it only writes the setting.am broadcast -a android.intent.action.AIRPLANE_MODEis rejected; broadcasting that action is restricted to system callers, soadbexits 255.Two notes:
0.20.0and0.20.10tarballs and this block is identical (modulo minified identifier renaming), so upgrading does not help.wificase has a related gap.settings wifi offrunssvc wifi disable, which genuinely disables Wi-Fi, but an emulator then falls back to cellular and stays online — so it also cannot be used to reach an offline state.Suggested fix
cmd connectivity airplane-mode <enable|disable>works on Android 11+, needs no root, and actually drives the connectivity stack. Verified on the same API 37 emulator:Round trip back to
disablerestores connectivity cleanly. Latency from issuing the command to traffic actually failing was measured at 65–365ms across trials.Suggested shape: try
cmd connectivity airplane-modefirst, and fall back to the existingsettings put+ broadcast pair only if that command is unavailable, so old devices keep whatever behaviour they have today. If the fallback is kept, it would be worth ordering the broadcast before thesettings put, or reverting the setting on broadcast failure, so a failure cannot leaveairplane_mode_oninconsistent with actual connectivity.Related: no
setAirplaneModein the Maestro dialectPossibly a separate issue, but it is the same "Maestro parity" shape as #2159.
--maestrosetAirplaneModetoggleAirplaneModeMaestro documents both commands as Android-supported. In
--maestromode, neither is recognized, and therunScriptsandbox exposes onlyoutput,json, andhttp.post— norequire/process— so a flow cannot reachadbby any other route either.The practical effect is that a flow cannot change connectivity mid-run at all: offline coverage has to be split into separate flows driven by an external script, or routed through a host-side HTTP sidecar called via
http.post. Sincesettings airplanealready exists internally, wiring those two Maestro commands to it looks like a small addition once the underlying toggle works.Environment
agent-device0.20.0and0.20.10(same behaviour on both)ro.build.version.release=17/sdk=37