Skip to content

fix(android): retain requested intent ownership on ADB - #3555

Open
thymikee wants to merge 8 commits into
t3/android-manual-shell-3077from
t3/android-intent-ownership-3077
Open

thymikee wants to merge 8 commits into
t3/android-manual-shell-3077from
t3/android-intent-ownership-3077

Conversation

@thymikee

@thymikee thymikee commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Summary

ADB intent opens retain the requested handler's package for session identity and close admission. Launch controls reach native launch unchanged; bounded lookup projects complete intent arguments and validates native integer controls.

open settings --relaunch --launch-args -S --launch-args -R --launch-args 1 --launch-args --display --launch-args 0 keeps the resolved owner. Unknown ownership now fails with typed reason android-relaunch-owner-unresolved before closing or launching; use the exact installed package. Ordinary opens retain optional lookup.

Sixteen layer files; depends on #3554, related to #3077. Updated CLI/MCP help and session documentation.

Validation

Head: 22dc288c6174048e78c7750aea1f46b3150fac3a; parent f660374ef. Rebased onto main 15cfe9fa2, including merged #3590; all eight layer commits remain unchanged.

Same lifecycle regression command: pnpm exec vitest run --project unit-core packages/platform-android/src/lifecycle.test.ts.

Base ce260138b, regression added Head
1 failed / 10 passed 11 passed

Alias-runner mutation fails the new identity assertion while the prior test passes. Frozen install/build and exact-head affected gates: pass (522 Vitest files / 4,053 tests). Independent review clear; review threads resolved. Integration passed; Coverage and smoke checks remain pending.

Native audit, source b69a3421a, proves API 23 typed owner refusal with zero native lifecycle effects. API 36's operation followed the deadline and is excluded from bounded proof. Cleanup remains incomplete: an expected stale claim and unverified emulator shutdown. Current-head proof is outstanding; private harness deadline, restoration and ownership gaps are under repair before measurement. Original iOS daemon-start failure is unexplained. Current size: +5.2 kB; independent review justified ownership design.

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.20 MB 5.20 MB +5.2 kB
Package (unpacked) 5.20 MB 5.20 MB +5.2 kB
Package (download) 1.56 MB 1.56 MB +1.6 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.0 ms 25.8 ms -1.2 ms
CLI --help 75.8 ms 74.9 ms -0.9 ms

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 8 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/platform-android/src/open-target-resolution.ts Outdated
@thymikee

Copy link
Copy Markdown
Member Author

I reviewed 14de349. The approach looks right, but the open Cubic thread on launch-only flags still applies, so this needs one more change before merge: #3555 (comment)

open settings --relaunch --launch-args -S (or -R, or --display) is forwarded verbatim to cmd package resolve-activity --brief at open-target-resolution.ts:34. From my reading of AOSP, that command takes an Intent grammar and rejects launch-only options, so the lookup would return undefined and the session package would stay unknown, which is the bug this PR fixes. It fails safe, with no wrong package. Could the lookup receive only the args that select the handler (-n/-p/-d/-t/-c/--user/extras)? One split in buildAndroidIntentArgs into intent-defining and launch-only args, used by both call sites, plus a test that passes a launch-only flag and still resolves the package, would cover it. If launch-only flags should leave ownership unknown on purpose, please say so in the PR instead.

Not blocking: open-target-resolution.ts:28 is a third hand-built resolve-activity plus parseAndroidLaunchComponent call, next to resolveAndroidLaunchComponent in app-lifecycle.ts and resolveAndroidLaunchComponentWithAdb in app-control.ts, and it repeats the android.intent.category.DEFAULT literal. Would one exported helper for all three be simpler?

The 19 checks are green, and the hosted Android job exercises the changed route. There are no conflicts. I did not run the lookup on a device, and the AOSP claim is from reading, not from a test.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/platform-android/src/intent-arguments.ts
Comment thread packages/platform-android/src/intent-arguments.ts
@thymikee

Copy link
Copy Markdown
Member Author

The 14de349 findings are now fixed in part: resolution no longer receives the launch-only flags, and the regression test asserts the stripped argv. Commit 2309542 still needs a live run, and the Coverage check fails on the eager-closure budget. Smoke Tests is still queued, so I can't attribute its result yet. Nothing else in the code blocks this.

The delta changes what reaches cmd package resolve-activity on a device: the projection, plus --query-flags 0x10000 in place of -c DEFAULT (open-target-resolution.ts). The PR body says hosted launch-control evidence for this head is pending, and the earlier hosted run was on 14de349. So no device has run the new argv. If a device rejects --query-flags, allowFailure turns that into silent unknown ownership on every intent open. Could you run open settings --relaunch --launch-args -S --launch-args -R --launch-args 1 --launch-args --display --launch-args 0 on an ADB Android emulator at 2309542? Please show the request or daemon log with am start -W -a android.settings.SETTINGS -S -R 1 --display 0, and with cmd package resolve-activity --brief --query-flags 0x10000 -a android.settings.SETTINGS exiting 0 with com.android.settings/... output. Please also show that the session appBundleId is com.android.settings, and repeat the resolve-activity call on the lowest supported API image.

Coverage fails because eager-closure-budgets now names mechanics.ts -> app-lifecycle.ts -> intent-arguments.ts. That edge is the new static import at app-lifecycle.ts:10, so this PR causes the failure. Could intent-arguments.ts return only the resolution arguments (for example androidIntentResolutionArgs(intent, launchArgs)), with app-lifecycle.ts keeping the verbatim launch argv as ['-a', intent, ...androidLaunchArgs(options)]? Then only the lazily imported open-target-resolution.ts depends on the grammar module. That also drops the {launchArgs, resolutionArgs} pair, where one half is identity, and it clears the budget without an approval.

Not blocking: the same move also keeps one module and 11 statements out of the mechanics.ts eager closure, and you can take it or leave it.

On the open threads: the cubic-dev-ai thread on resolution arguments is fixed at this head, so please resolve it (thread). The malformed --display x thread does not apply, because am start itself fails on a non-numeric value and the open rejects, and the stripped projection is the same intent minus launch controls (thread). The --selector thread does not apply either, because as I recall AOSP Intent.parseCommandArgs carries -t into the selector the same way, so a reset would diverge from am (thread).

Two limits on this review. I checked the AOSP grammar claims (option arities, --selector carry-over, --query-flags support and its minimum API level) from memory of the source, not from a source read or a device run. I also did not run the tests, and I did not confirm whether a failed dispatch after prepare-time resolution leaves an appBundleId on an existing --relaunch session. The --display thread's dismissal assumes it does not.

@thymikee

Copy link
Copy Markdown
Member Author

Thanks for the update. Since the earlier review (#3555 (comment)), the intent open now sends the verbatim launch args to am start and a projected resolve-activity call for ownership, and I found no code problem at 91b9fad. What remains is device evidence. The new argv is cmd package resolve-activity --brief --query-flags 0x10000 -a <intent> <projected args>, and no device has run it yet (open-target-resolution.ts#L24). If an image rejects --query-flags or the projection, allowFailure plus catch { return undefined } leaves appBundleId unset without any error, which is the bug this PR fixes. Please run this on an ADB Android emulator at 2309542 or later (91b9fad only changes module loading, so either head counts): open settings --relaunch --launch-args -S --launch-args -R --launch-args 1 --launch-args --display --launch-args 0. The request or daemon log should show am start -W -a android.settings.SETTINGS -S -R 1 --display 0, and cmd package resolve-activity --brief --query-flags 0x10000 -a android.settings.SETTINGS exiting 0 with com.android.settings/... output. The session should then report appBundleId as com.android.settings. Please repeat the resolve-activity call on the lowest supported API image.

Not blocking, take or leave: app-lifecycle.ts#L292 now lazy-loads the 186-line grammar module on every intent launch only to read .launchArgs, which is the same ['-a', intent, ...launchArgs] that app-lifecycle already built before this PR. Would it be simpler to keep ['-a', intent, ...androidLaunchArgs(options)] there and export only the resolution projection from intent-arguments.ts, so only open-target-resolution.ts depends on it?

Of the two open inline threads, I think neither applies. The --display/-R thread does not apply because launch argv stays verbatim, so a malformed control makes am start fail and the open rejects. The --selector data/type thread does not apply because, as far as I recall, am also lets the selector inherit -t, and the suggested reset would diverge from it. That second point is from memory, not from a source read.

Android Smoke fails at open <fixture appId> --relaunch <url> then wait text Automation lab with wait_capture_stalled. This looks unrelated: the fixture open takes the unchanged package branch, and the new launchArgs is used only on the intent branch. I did not open the Smoke artifacts, and the second Smoke job and Typecheck & Package had not finished. I did not run the unit tests either. Before merge, the PR needs the emulator run above.

@thymikee
thymikee force-pushed the t3/android-intent-ownership-3077 branch from dc4d7ee to ce26013 Compare October 13, 2026 11:46
@thymikee

Copy link
Copy Markdown
Member Author

At ce26013 the earlier typed-value and launch-only-flag threads are fixed, but one check from the first review at 91b9fad is still missing. I could not tell from the code whether cmd package resolve-activity --brief --query-flags 0x10000 -a <intent> <projected args> works on a real device. With allowFailure and the catch that returns undefined in open-target-resolution.ts, a device that rejects --query-flags or the projection leaves appBundleId unset without any error, which is the bug this PR fixes. The fix could be a no-op on real ADB devices and no test would catch it. Please run this on an ADB Android emulator at ce26013: open settings --relaunch --launch-args -S --launch-args -R --launch-args 1 --launch-args --display --launch-args 0. The log must show am start -W -a android.settings.SETTINGS -S -R 1 --display 0 and the resolve-activity --brief --query-flags 0x10000 -a android.settings.SETTINGS call exiting 0 with com.android.settings/... output, and the session must report appBundleId as com.android.settings. Please also repeat the resolve-activity call on the lowest supported API image.

Not blocking: the new java-int validator in intent-arguments.ts is a 15-line Unicode Integer.parseInt replica. am start already parses the verbatim argv and rejects a malformed integer, so the open fails either way. Would an ASCII check (/^[+-]?\d+$/ plus the int32 bound) that treats any other digits as unprojectable cover the --relaunch force-stop case, and fail safe to unknown ownership? You can take this or leave it.

Of the open inline threads, the launch-only flags thread and the malformed integer thread are fixed at this commit, so please resolve them. The selector MIME thread does not apply, because keeping data and type across --selector matches AOSP argument parsing and the test pins it, so please resolve that one too.

The failing check is the iOS smoke preflight, where prepare ios-runner hit daemon_startup_failed on a very slow host. This PR changes only Android intent projection and open-target modules, so I think the failure is unrelated, and a second Smoke Tests run is still queued. I did not run the new tests, and I could not read the daemon log of that job. Once the emulator run above is posted, this is ready for another look.

@thymikee

Copy link
Copy Markdown
Member Author

I ran one targeted private workflow against published source ce260138b8838fee2c18cdf023663f0d2de349f7 (run). The API36 native command/ownership conditions are satisfied; the lowest-API condition is not satisfied, and the workflow is red.

API36 job / raw artifact: actual source CLI command was:

node --experimental-strip-types src/bin.ts open settings --relaunch --launch-args -S --launch-args -R --launch-args 1 --launch-args --display --launch-args 0 --platform android --serial emulator-5554 --session pr3555-round30-api36 --state-dir /home/runner/work/agent-device/agent-device/.tmp/pr3555-round30-api36-state --json

CLI exit0, data.appBundleId is com.android.settings. The observer at the SDK-selected adb path recorded these actual native executions, each exit0 with empty stderr:

adb -s emulator-5554 shell cmd package resolve-activity --brief --query-flags 0x10000 -a android.settings.SETTINGS
stdout:
priority=1 preferredOrder=0 match=0x108000 specificIndex=-1 isDefault=true
com.android.settings/.homepage.SettingsHomepageActivity
exit: 0

adb -s emulator-5554 shell am start -W -a android.settings.SETTINGS -S -R 1 --display 0
stdout:
Stopping: com.android.settings
Starting: Intent { act=android.settings.SETTINGS }
Status: ok
LaunchState: COLD
Activity: com.android.settings/.homepage.SettingsHomepageActivity
TotalTime: 4496
WaitTime: 4499
Complete
exit: 0

The subsequent snapshot reports androidSnapshot.backend=android-helper, helperVersion=0.21.28-dev, helperApiVersion=2. Original native adb SHA256 a902be8f45c6c62e76c9efaf6947a0fa747c9cabd89a2ac8e0d16ecb30b3ed01 was restored after session close and daemon stop.

API23 job / raw artifact: both source helper manifests declare minSdk23/targetSdk36, and device getprop ro.build.version.sdk returned23. The same native resolver argv was measured directly on its separate emulator/job:

/usr/local/lib/android/sdk/platform-tools/adb -s emulator-5554 shell cmd package resolve-activity --brief --query-flags 0x10000 -a android.settings.SETTINGS
stdout: /system/bin/sh: cmd: not found

stderr: (empty)
adb exit: 0
proof condition: UNSUPPORTED / FAILED
measurement process exit: 1

Exit0 on this older adb shell does not establish command support. No Settings component was returned. The owning resolver (packages/platform-android/src/open-target-resolution.ts:24-45) uses this query and returns undefined for noncomponent output. API23 public CLI open was not exercised, so this proves a capability gap rather than an observed admission failure. The minimum condition remains open for a behavior decision; no fallback was added.

Limitations retained: the API36 cleanup harness incorrectly supplied --state-dir to daemonless device status (exit1 INVALID_ARGS). Session close and graceful daemon stop/clean exited0, but both post-emulator listings still contained offline entries, so complete final cleanup is unverified. This is targeted manual evidence with a host adb observer that perturbs timing, not automatic full-suite coverage or a performance claim. Both archive digests and per-file hashes were verified. No retry was dispatched.

@thymikee

Copy link
Copy Markdown
Member Author

Thanks for running both images. At ce26013, the API 36 run proves the fix: resolve-activity returns com.android.settings/... and the session reports appBundleId as com.android.settings.

The API 23 run shows the gap is real on the lowest supported image. cmd does not exist there, so the query in open-target-resolution.ts prints cmd: not found, adb still exits 0, and the resolver returns undefined. On API 23 an intent open will still leave appBundleId unset without any error, which is the bug this PR fixes. Could the resolver fall back to a query that API 23 supports, or report the ownership as unresolved, so the gap is not silent? Then please re-run the API 23 open and confirm that the session reports either com.android.settings or the unresolved state.

The failing Smoke Tests check is reported separately and does not change this verdict.

@thymikee

Copy link
Copy Markdown
Member Author

The API 23 result exposed an unsafe admission case, beyond the missing proof: when the owner query cannot produce a package, relaunch could still close the hardcoded Settings alias. Fixed in 826d947dd: Android over ADB now refuses with COMMAND_FAILED, details.reason: android-relaunch-owner-unresolved, before resolving an interactor, applying hints, closing, or launching. A resolved owner is the only package admitted for close. Ordinary opens keep their optional lookup; no compatibility or foreground fallback was added.

The new regression failed on old production (1 failed / 10 passed), then passed (11 passed). Known vendor-owner and explicit-package controls verify close targets. Full exact-head affected gates pass: 529 files / 4,064 tests. The broader gate also exposed ownership-free test fixtures and a close-failure test that could pass before reaching close; the owning fixtures and actual close-dispatch assertion are repaired. CLI/MCP help and session docs describe the requirement.

The earlier native proof retains its ce260138b attribution: API 36 passes; API 23 lacks the query. New-head CI and live public-route refusal proof remain pending. This does not claim successful API 23 ownership resolution.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 7 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread packages/platform-android/src/lifecycle.test.ts
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://callstack.github.io/agent-device/pr-preview/pr-3555/

Built to branch gh-pages at 2026-10-13 19:07 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@thymikee

Copy link
Copy Markdown
Member Author

This round did not obtain the requested live proof. I verified PR #3555's remote head as 826d947dddf855dd33d01b5f2e66661d2fd23126 before one private hosted dispatch: run 38437663023.

The API23 job aborted at a private harness prerequisite: it required the owned state directory to be absent after preparation had already created it. The requested public open, native resolver and SDK ADB observation never ran. This establishes neither a product failure nor the new typed refusal. I canceled the API36 job while it was still queued; it had no runner or measurement. No redispatch was made.

The API23 failure artifact is retained: its archive digest matches GitHub and all 25 file digests verify. Cleanup records show no sessions or device claims, clean graceful daemon shutdown with no pending releases, and emulator process exit followed by device-not-found. No alias close was issued. The observer was never installed; ADB start/end hashes and subject request/native records were not captured.

API36 positive ownership and API23 explicit refusal remain unverified by this round. It does not establish successful minimum-API ownership or a compatibility fallback. Historical round30 evidence is not substituted for the pinned head.

@thymikee
thymikee force-pushed the t3/android-intent-ownership-3077 branch from b69a342 to 22dc288 Compare October 13, 2026 18:20
@thymikee

Copy link
Copy Markdown
Member Author

Completed the private native audit on immutable runtime b69a3421a7d32d801540994b827b09dcf6cc5e70. This is historical evidence; the rebased PR head 22dc288c6 was not tested.

  • API 23 proves the actual public CLI returns COMMAND_FAILED / android-relaunch-owner-unresolved. Its native resolver returns /system/bin/sh: cmd: not found with ADB exit 0. All 13 observed ADB invocations completed; none issued native close or launch. This proves refusal, not successful minimum-API ownership.
  • API 36 recorded the expected owner, native launch controls, and helper route, but its operation ran after the deadline and accepted cancellation. It is excluded from the bounded proof. Its raw request log was not retained.
  • Cleanup is incomplete: both daemons/sessions cleared and SDK ADB was restored byte-for-byte. API 23 retained an owner-process-dead claim for pr3555-round32-api23, state /tmp/agent-device-replay-daemon-rsjAFq; its emulator process exited but the serial remained offline. API 36's emulator process and available serial persisted through the final checks. Hosted VM disposal is unverified.

API 23 artifact and API 36 artifact: both archive SHA256 hashes and all 242 inner digests verified. One dispatch; no source changes, fallback, redispatch, or merge. Current-head verification remains outstanding; the harness cleanup and measurement-deadline boundaries need repair before another measurement.

@thymikee

Copy link
Copy Markdown
Member Author

The current-head private verification run, targeting 22dc288c6174048e78c7750aea1f46b3150fac3a, was canceled before device allocation after a combined late-entry preflight test found a cleanup gap. Both API jobs report runner ID 0 and no steps; no artifacts or product measurements were produced. This adds no API 23 refusal or API 36 positive proof on the current head.

The gap was in the private harness: the first deadline-guarded query could reject before emulator identity was recorded, leaving cleanup unable to stop its owned emulator. The unpublished repair records identity before measurement and retains it through failure. The same controlled late-entry case now refuses measurement and proves owned stop/serial unavailability. Independent harness review is underway before any further measurement.

The run is terminal with no child work. No hosted sessions, daemons, claims or observers were created; local preflight processes and daemon were cleaned. Shipping source and branches were untouched. Earlier successful refusal evidence remains attributed to b69a3421a; current-head verification remains outstanding.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant