[GH-ISSUE #2225] [Bug] ADB activity startup treats Status: timeout as a successful launch #744

Closed
opened 2026-10-15 18:27:53 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @dakjdakd on GitHub (Oct 12, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2225

What happens

Both start_adb_app and start_adb_activity can return successful Evidence with confidence: "observed" when Android's Activity Manager explicitly reports Status: timeout. The same early-success path can also hide an Error: or Exception printed after Starting:.

This matters in a dynamic-analysis workflow: a caller may start an app, then collect its processes, logs, or behavior under the assumption that the launch completed. A timeout means the wait did not establish completion. It does not establish that no activity was started, and it does not undo the device mutation already requested. REA needs to preserve the attempt while making the unsuccessful verdict visible.

The implementation is available in the accompanying draft pull request.

Report context

Field Context
Area Process capture or runtime observation; Evidence outcome integrity
REA source revision Affected upstream revision e4d3e1399d9cd4932653e7015a1bf8589e56029d; proposed fix 18c82f610eb63b5f1a14c18b89333befbc5f438a
Runtime and platform Node.js 24.18.0, npm 11.16.0, Windows x64
Provider and target context ADB activity-launch protocol; an installed Android package or an explicit Activity Manager intent
Interfaces MCP start_adb_app and start_adb_activity; the shared provider/application behavior also supplies CLI results
Earliest failure stage Capture or observation: parseAmStartOutput interprets the opening Starting line before evaluating the complete launch report

Android context and why exit status is insufficient

REA invokes adb shell am start -W. The -W option asks Activity Manager to wait for the activity launch and report the wait result. There are separate facts in this text protocol:

  • Starting: Intent { ... } describes the intent about to be submitted.
  • Status: ok or Status: timeout describes the Activity Manager wait result.
  • Activity: ... identifies the activity reported by that result, when available.
  • Complete marks the end of the command's report; it also appears after a timeout.

The Android 14 source makes the ordering explicit. runStartActivity prints and flushes Starting: before making the start call. With -W, it then calls startActivityAndWait. Later, the report prints Status: from result.timeout, followed by launch information and Complete. The method returns 0 after emitting the report.

Consequently, an invocation can finish normally at the process boundary while reporting a device-side timeout. Treating the host command's success or its opening line as proof of launch completion loses the controlling part of the protocol.

Affected calls and the smallest trigger

For start_adb_app, provide an installed package whose launcher resolution succeeds:

{
  "serial": "emulator-5554",
  "package": "com.example"
}

The provider first runs cmd package resolve-activity --brief -c android.intent.category.LAUNCHER com.example. In the regression fixture, this returns com.example/.Main, so the next command is:

adb -s emulator-5554 shell am start -W -n com.example/.Main

For start_adb_activity, the equivalent explicit intent is:

{
  "serial": "emulator-5554",
  "action": "android.intent.action.MAIN",
  "component": "com.example/.Main",
  "extras": []
}

This goes directly to:

adb -s emulator-5554 shell am start -W -a android.intent.action.MAIN -n com.example/.Main

Both operations reach the same AdbProvider.runAmStart and parseAmStartOutput boundary. The complete stdout fixture below, with empty stderr and a normally completed invocation, triggers the problem:

Starting: Intent { act=android.intent.action.MAIN cmp=com.example/.Main }
Status: timeout
Activity: com.example/.Main
WaitTime: 10004
Complete

This is the deterministic protocol fixture used in the regression. Android may include additional LaunchState and TotalTime lines; none of those lines changes the meaning of Status: timeout.

Current result

parseAmStartOutput currently returns status: "started" immediately after matching Starting:. It does not reach the later timeout or error lines. runAmStart therefore returns a successful AnalysisExecution, and the shared application service wraps it in observed Evidence.

The relevant fields of the old start_adb_app response are:

{
  "confidence": "observed",
  "normalized_result": {
    "client": {
      "binary_path": "adb",
      "path_source": "path",
      "version": "34.0.5-stub",
      "installed_path": null
    },
    "serial": "emulator-5554",
    "package_name": "com.example",
    "component": "com.example/.Main",
    "started_activity": "act=android.intent.action.MAIN cmp=com.example/.Main"
  },
  "raw_result": null
}

This excerpt omits unrelated Evidence envelope fields. The misleading behavior is the successful observed result: the device's timeout is absent, and the command output is not retained. The legacy started_activity value is an Intent payload, not the separately reported Activity: component.

Expected result

The parser should read the complete output before deciding the outcome. Status: timeout must result in a failure, preserving the attempted Intent and any reported Activity/Status. Hard Error: or Exception lines should take precedence over success and timeout classification, including errors emitted on stderr. An unknown non-ok status should not silently become a successful launch.

For the fixture above, the proposed implementation returns an MCP error response (isError: true) with this JSON body:

{
  "error": {
    "code": "unsupported_target",
    "category": "unsupported_target",
    "message": "Unsupported target for start_adb_app at emulator-5554: am start reported status: timeout",
    "retryable": false,
    "remediation": {
      "action": "Select a target supported by this operation or choose an operation supporting the reported target format."
    },
    "details": {
      "operation": "start_adb_app",
      "path": "emulator-5554",
      "reason": "am start reported status: timeout",
      "captured_output": {
        "stdout": "Starting: Intent { act=android.intent.action.MAIN cmp=com.example/.Main }\nStatus: timeout\nActivity: com.example/.Main\nWaitTime: 10004\nComplete\n",
        "stderr": "",
        "truncated": false
      },
      "partial_observation": {
        "provider_id": "adb",
        "operation": "start_adb_app",
        "result": {
          "client": {
            "binary_path": "adb",
            "path_source": "path",
            "version": "34.0.5-stub",
            "installed_path": null
          },
          "serial": "emulator-5554",
          "package_name": "com.example",
          "component": "com.example/.Main",
          "started_activity": "act=android.intent.action.MAIN cmp=com.example/.Main",
          "reported_activity": "com.example/.Main",
          "reported_status": "timeout"
        }
      }
    }
  }
}

For start_adb_activity, the same error path retains that operation's action, URI, component, and typed extras instead of a package-launcher result. Its partial result for the call above includes:

{
  "serial": "emulator-5554",
  "action": "android.intent.action.MAIN",
  "data_uri": null,
  "component": "com.example/.Main",
  "extras": [],
  "started_activity": "act=android.intent.action.MAIN cmp=com.example/.Main",
  "reported_activity": "com.example/.Main",
  "reported_status": "timeout"
}

The shared CLI/MCP workflow receives a typed failure instead of successful observed Evidence. The partial observation records the launch attempt and its facts; it does not claim that the activity completed, remained running, or was rolled back.

The existing unsupported_target classification is intentional in this focused fix. AnalysisTimeoutError represents a declared execution deadline and requires a numerical timeoutMs, which is exposed as timeout_ms. Activity Manager's Status: timeout does not report that deadline. WaitTime: 10004 is elapsed command time, and REA's separate 30-second subprocess limit is not the device's reported deadline. Using either as timeoutMs would invent a fact. The fix keeps the existing device-refusal error class and its generic remediation, with the specific timeout reason and structured reported_status visible in the error details. Host-process deadline handling remains a separate path.

Successful launches and compatibility

Status: ok must remain successful. Android also emits legitimate warnings when an intent is delivered to an already-running activity or an existing task is brought forward. A Warning: alongside a valid Starting line and an ok status should not be reclassified as a hard failure. The existing Starting-only success form remains accepted for compatibility when there is no hard error or contradictory status.

The proposed result schemas keep started_activity and add optional nullable reported_activity and reported_status fields. Existing results without the new fields remain valid. The provider fills these fields with parsed values or explicit nulls, and successful Evidence retains the complete stdout/stderr in raw_result. This lets callers distinguish the requested intent, the device's concrete activity report, and the status without reparsing the raw text.

Impact and scope

This affects the shared Activity Manager launch-result interpretation for these two tools, including what callers are told about a requested device mutation. It affects Evidence accuracy and follow-up workflow decisions, without changing authority to start apps or intents. The proposed fix does not change launcher resolution, typed-extra parsing, remote-shell quoting, process execution budgets, subprocess timeout/cancellation handling, cleanup, or the package install/uninstall operations. It also does not attempt to infer a later lifecycle state from the wait result.

Regression evidence and verification limits

The local Windows regression executes the actual AdbProvider, shared application service, MCP server, and MCP client, with only the process-output boundary simulated. For both start tools it checks normal success, timeout failure with retained partial facts, a later stderr error, the successful raw-output record, and the exact am start -W argument vector. Parser tests additionally cover LF/CRLF output, earlier ok followed by failure, error precedence, Starting-only compatibility, and already-running warnings.

The exact focused selection passed 14 tests. npm run check, npm run check:fast, npm run docs:check, and npm run verify:test-discovery are recorded in the linked PR's validation section. Local verification covers Windows deterministic process-protocol fixtures and the production provider-to-MCP boundary; live Android lifecycle/timeout behavior remains outside local coverage and is the remaining provider-lane check for the draft PR.

Originally created by @dakjdakd on GitHub (Oct 12, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2225 ## What happens Both `start_adb_app` and `start_adb_activity` can return successful Evidence with `confidence: "observed"` when Android's Activity Manager explicitly reports `Status: timeout`. The same early-success path can also hide an `Error:` or `Exception` printed after `Starting:`. This matters in a dynamic-analysis workflow: a caller may start an app, then collect its processes, logs, or behavior under the assumption that the launch completed. A timeout means the wait did not establish completion. It does not establish that no activity was started, and it does not undo the device mutation already requested. REA needs to preserve the attempt while making the unsuccessful verdict visible. The implementation is available in the accompanying [draft pull request](https://github.com/morluto/rea/pull/2226). ## Report context | Field | Context | | --- | --- | | Area | Process capture or runtime observation; Evidence outcome integrity | | REA source revision | Affected upstream revision `e4d3e1399d9cd4932653e7015a1bf8589e56029d`; proposed fix `18c82f610eb63b5f1a14c18b89333befbc5f438a` | | Runtime and platform | Node.js 24.18.0, npm 11.16.0, Windows x64 | | Provider and target context | ADB activity-launch protocol; an installed Android package or an explicit Activity Manager intent | | Interfaces | MCP `start_adb_app` and `start_adb_activity`; the shared provider/application behavior also supplies CLI results | | Earliest failure stage | Capture or observation: `parseAmStartOutput` interprets the opening Starting line before evaluating the complete launch report | ## Android context and why exit status is insufficient REA invokes `adb shell am start -W`. The `-W` option asks Activity Manager to wait for the activity launch and report the wait result. There are separate facts in this text protocol: - `Starting: Intent { ... }` describes the intent about to be submitted. - `Status: ok` or `Status: timeout` describes the Activity Manager wait result. - `Activity: ...` identifies the activity reported by that result, when available. - `Complete` marks the end of the command's report; it also appears after a timeout. The Android 14 source makes the ordering explicit. [`runStartActivity` prints and flushes `Starting:` before making the start call](https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-14.0.0_r1/services/core/java/com/android/server/am/ActivityManagerShellCommand.java#660). With `-W`, it then calls [`startActivityAndWait`](https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-14.0.0_r1/services/core/java/com/android/server/am/ActivityManagerShellCommand.java#719). Later, [the report prints `Status:` from `result.timeout`, followed by launch information and `Complete`](https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-14.0.0_r1/services/core/java/com/android/server/am/ActivityManagerShellCommand.java#802). The method [returns `0` after emitting the report](https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-14.0.0_r1/services/core/java/com/android/server/am/ActivityManagerShellCommand.java#819). Consequently, an invocation can finish normally at the process boundary while reporting a device-side timeout. Treating the host command's success or its opening line as proof of launch completion loses the controlling part of the protocol. ## Affected calls and the smallest trigger For `start_adb_app`, provide an installed package whose launcher resolution succeeds: ```json { "serial": "emulator-5554", "package": "com.example" } ``` The provider first runs `cmd package resolve-activity --brief -c android.intent.category.LAUNCHER com.example`. In the regression fixture, this returns `com.example/.Main`, so the next command is: ```text adb -s emulator-5554 shell am start -W -n com.example/.Main ``` For `start_adb_activity`, the equivalent explicit intent is: ```json { "serial": "emulator-5554", "action": "android.intent.action.MAIN", "component": "com.example/.Main", "extras": [] } ``` This goes directly to: ```text adb -s emulator-5554 shell am start -W -a android.intent.action.MAIN -n com.example/.Main ``` Both operations reach the same `AdbProvider.runAmStart` and `parseAmStartOutput` boundary. The complete stdout fixture below, with empty stderr and a normally completed invocation, triggers the problem: ```text Starting: Intent { act=android.intent.action.MAIN cmp=com.example/.Main } Status: timeout Activity: com.example/.Main WaitTime: 10004 Complete ``` This is the deterministic protocol fixture used in the regression. Android may include additional `LaunchState` and `TotalTime` lines; none of those lines changes the meaning of `Status: timeout`. ## Current result `parseAmStartOutput` currently returns `status: "started"` immediately after matching `Starting:`. It does not reach the later timeout or error lines. `runAmStart` therefore returns a successful `AnalysisExecution`, and the shared application service wraps it in observed Evidence. The relevant fields of the old `start_adb_app` response are: ```json { "confidence": "observed", "normalized_result": { "client": { "binary_path": "adb", "path_source": "path", "version": "34.0.5-stub", "installed_path": null }, "serial": "emulator-5554", "package_name": "com.example", "component": "com.example/.Main", "started_activity": "act=android.intent.action.MAIN cmp=com.example/.Main" }, "raw_result": null } ``` This excerpt omits unrelated Evidence envelope fields. The misleading behavior is the successful observed result: the device's timeout is absent, and the command output is not retained. The legacy `started_activity` value is an Intent payload, not the separately reported `Activity:` component. ## Expected result The parser should read the complete output before deciding the outcome. `Status: timeout` must result in a failure, preserving the attempted Intent and any reported Activity/Status. Hard `Error:` or `Exception` lines should take precedence over success and timeout classification, including errors emitted on stderr. An unknown non-`ok` status should not silently become a successful launch. For the fixture above, the proposed implementation returns an MCP error response (`isError: true`) with this JSON body: ```json { "error": { "code": "unsupported_target", "category": "unsupported_target", "message": "Unsupported target for start_adb_app at emulator-5554: am start reported status: timeout", "retryable": false, "remediation": { "action": "Select a target supported by this operation or choose an operation supporting the reported target format." }, "details": { "operation": "start_adb_app", "path": "emulator-5554", "reason": "am start reported status: timeout", "captured_output": { "stdout": "Starting: Intent { act=android.intent.action.MAIN cmp=com.example/.Main }\nStatus: timeout\nActivity: com.example/.Main\nWaitTime: 10004\nComplete\n", "stderr": "", "truncated": false }, "partial_observation": { "provider_id": "adb", "operation": "start_adb_app", "result": { "client": { "binary_path": "adb", "path_source": "path", "version": "34.0.5-stub", "installed_path": null }, "serial": "emulator-5554", "package_name": "com.example", "component": "com.example/.Main", "started_activity": "act=android.intent.action.MAIN cmp=com.example/.Main", "reported_activity": "com.example/.Main", "reported_status": "timeout" } } } } } ``` For `start_adb_activity`, the same error path retains that operation's action, URI, component, and typed extras instead of a package-launcher result. Its partial `result` for the call above includes: ```json { "serial": "emulator-5554", "action": "android.intent.action.MAIN", "data_uri": null, "component": "com.example/.Main", "extras": [], "started_activity": "act=android.intent.action.MAIN cmp=com.example/.Main", "reported_activity": "com.example/.Main", "reported_status": "timeout" } ``` The shared CLI/MCP workflow receives a typed failure instead of successful observed Evidence. The partial observation records the launch attempt and its facts; it does not claim that the activity completed, remained running, or was rolled back. The existing `unsupported_target` classification is intentional in this focused fix. `AnalysisTimeoutError` represents a declared execution deadline and requires a numerical `timeoutMs`, which is exposed as `timeout_ms`. Activity Manager's `Status: timeout` does not report that deadline. `WaitTime: 10004` is elapsed command time, and REA's separate 30-second subprocess limit is not the device's reported deadline. Using either as `timeoutMs` would invent a fact. The fix keeps the existing device-refusal error class and its generic remediation, with the specific timeout reason and structured `reported_status` visible in the error details. Host-process deadline handling remains a separate path. ## Successful launches and compatibility `Status: ok` must remain successful. Android also emits legitimate warnings when an intent is delivered to an already-running activity or an existing task is brought forward. A `Warning:` alongside a valid Starting line and an `ok` status should not be reclassified as a hard failure. The existing Starting-only success form remains accepted for compatibility when there is no hard error or contradictory status. The proposed result schemas keep `started_activity` and add optional nullable `reported_activity` and `reported_status` fields. Existing results without the new fields remain valid. The provider fills these fields with parsed values or explicit nulls, and successful Evidence retains the complete stdout/stderr in `raw_result`. This lets callers distinguish the requested intent, the device's concrete activity report, and the status without reparsing the raw text. ## Impact and scope This affects the shared Activity Manager launch-result interpretation for these two tools, including what callers are told about a requested device mutation. It affects Evidence accuracy and follow-up workflow decisions, without changing authority to start apps or intents. The proposed fix does not change launcher resolution, typed-extra parsing, remote-shell quoting, process execution budgets, subprocess timeout/cancellation handling, cleanup, or the package install/uninstall operations. It also does not attempt to infer a later lifecycle state from the wait result. ## Regression evidence and verification limits The local Windows regression executes the actual `AdbProvider`, shared application service, MCP server, and MCP client, with only the process-output boundary simulated. For both start tools it checks normal success, timeout failure with retained partial facts, a later stderr error, the successful raw-output record, and the exact `am start -W` argument vector. Parser tests additionally cover LF/CRLF output, earlier `ok` followed by failure, error precedence, Starting-only compatibility, and already-running warnings. The exact focused selection passed 14 tests. `npm run check`, `npm run check:fast`, `npm run docs:check`, and `npm run verify:test-discovery` are recorded in the linked PR's validation section. Local verification covers Windows deterministic process-protocol fixtures and the production provider-to-MCP boundary; live Android lifecycle/timeout behavior remains outside local coverage and is the remaining provider-lane check for the draft PR.
bsakaguchi 2026-10-15 18:27:53 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#744
No description provided.