[Bug Report]: ADB operator reports success before asynchronous actions finish

Author: dajiaohuangCreated Aug 22, 2026Updated Aug 23, 2026

Version

Current main at c2ad42e3eb9b27830db41a3e6f51ca7179d9b168; @gui-agent/operator-adb 0.3.0.

Issue Type

  • Other: GUI Agent ADB operator (@gui-agent/operator-adb)

Model Provider

Not applicable.

Problem Description

Several asynchronous ADB actions are started without being awaited by AdbOperator.singleActionExecutor(). As a result, execute() can report success before the device action finishes, and a later rejection can become detached from the action request.

Current affected paths in multimodal/gui-agent/operator-adb/src/AdbOperator.ts include:

  • long_press: calls async handleSwipe(...) without await
  • swipe / drag: calls async handleSwipe(...) without await
  • scroll: calls async handleScroll(...) without await; handleScroll() then also calls async handleSwipe(...) without await
  • type: calls async handleType(...) without await
  • handleHotkey(): calls the ADB keyevent(...) promise without await

The public execute() loop does await singleActionExecutor(action), but that does not help when the individual branch returns before its device promise settles.

Provider- and device-independent reproduction with a delayed ADB mock:

typescript
const pending = deferred<void>();
mockAdb.shell.mockReturnValueOnce(pending.promise);

const execution = operator.doExecute({ actions: [swipeAction] });

// Expected: execution is pending until the ADB shell operation settles.
// Actual: execution resolves successfully while pending.promise is unresolved.
await expect(Promise.race([execution.then(() => 'resolved'), tick().then(() => 'pending')]))
  .resolves.toBe('resolved');

pending.reject(new Error('adb failed'));
// The rejection is no longer propagated through execute().

Expected: every action's returned promise resolves only after its underlying ADB operation completes, and ADB failures reject execute().

Actual: the affected branches return early, allowing later actions to overlap and allowing execute() to return { status: 'success' } before the device has finished.

Impact: multi-action plans can execute out of order; screenshots may be taken before the preceding gesture/type completes; and device command failures may surface as unhandled rejections instead of an operator error.

This is issue-only in this contribution round because there is no Android/ADB device available to validate real command ordering and failure behavior, and open security PR #1883 modifies the same implementation. A follow-up PR should coordinate with that work, add delayed/rejecting ADB mocks for every affected branch, and run at least one device-level smoke test for swipe, type and key events.

Error Logs

No synchronous error is guaranteed. The observable failure is early success followed by a detached rejection from the mocked or real ADB promise.

Source: bytedance/UI-TARS-desktop