Skip to content

fix(android): refuse relaunch when close fences expire - #3562

Open
thymikee wants to merge 1 commit into
t3/settings-replay-corpus-3077from
t3/android-close-fences-3077
Open

thymikee wants to merge 1 commit into
t3/settings-replay-corpus-3077from
t3/android-close-fences-3077

Conversation

@thymikee

@thymikee thymikee commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Summary

Android close could report success after its two-second foreground or process fence expired. Consequently, agent-device open settings --relaunch could launch without establishing that Settings stopped. Both fences now return typed COMMAND_FAILED / android-app-close-timeout with phase, package and budget details. Relaunch stops before open or observation after refusal.

Three files; existing budgets, polling and stable-process checks remain. Related to #3077, stacked on #3560. This enforces the existing restart promise; it does not diagnose native persistence or decide readiness.

Validation

Tested head: 5d05dc21eb6873d666326305a78b9ab1aff437c4.

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

Base 30942efb Head 5d05dc21
3 failed / 32 passed 35 passed

Fresh install/build, format, Fallow, independent review and exact affected gate pass: 355 related files / 2,379 tests. CI: 16 passed, one skipped, including Coverage and Integration.

Android live passes full fixtures and 6/6 ordinary cases, 87 actions, first attempts, zero healing; seven Settings relaunches complete close before launch. Native stopped-state logs and live timeout refusal were absent; refusal is regression-test evidence. Separate iOS startup preflight fails, so the whole manual workflow is not green. Cleanup is observed; post-shutdown host leases are unobserved.

@thymikee
thymikee added this pull request to stack #3556 October 13, 2026 03:59
@github-actions

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.17 MB 5.17 MB +340 B
Package (unpacked) 5.17 MB 5.17 MB +340 B
Package (download) 1.55 MB 1.55 MB +86 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.3 ms 26.9 ms -0.4 ms
CLI --help 76.8 ms 76.6 ms -0.2 ms

@thymikee
thymikee marked this pull request as ready for review October 13, 2026 04:20

@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.

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/platform-android/src/app-lifecycle.ts">

<violation number="1" location="packages/platform-android/src/app-lifecycle.ts:555">
P2: Both fences can still succeed after expiry when an in-flight probe returns “not foreground” or “no process” after the deadline; the early return bypasses this timeout. Check the deadline after each awaited probe before accepting success, so relaunch refuses when the fence expires.</violation>
</file>

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

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

if (foreground?.package !== packageName) return;
await sleep(ANDROID_CLOSE_FOCUS_POLL_MS);
}
throw new AppError(

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.

P2: Both fences can still succeed after expiry when an in-flight probe returns “not foreground” or “no process” after the deadline; the early return bypasses this timeout. Check the deadline after each awaited probe before accepting success, so relaunch refuses when the fence expires.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/platform-android/src/app-lifecycle.ts, line 555:

<comment>Both fences can still succeed after expiry when an in-flight probe returns “not foreground” or “no process” after the deadline; the early return bypasses this timeout. Check the deadline after each awaited probe before accepting success, so relaunch refuses when the fence expires.</comment>

<file context>
@@ -552,6 +552,16 @@ async function waitForAndroidPackageNotForeground(
     if (foreground?.package !== packageName) return;
     await sleep(ANDROID_CLOSE_FOCUS_POLL_MS);
   }
+  throw new AppError(
+    'COMMAND_FAILED',
+    `Android app ${packageName} remained foreground after close`,
</file context>

@thymikee

Copy link
Copy Markdown
Member Author

Reviewed 5d05dc2. I found no problems in the change: an expired close fence is now a typed refusal, and closeAndroidApp in the Android app lifecycle module is the right owner for it.

The open cubic P2 thread on the deadline at app-lifecycle.ts lines 552 and 578-580 does not apply, so please resolve it. Those returns fire only on a positive stop observation; every other outcome reaches the throw once the loop ends. A late probe that sees the app stopped has still confirmed the close, so refusing it would turn a real close into a false failure. #3562 (comment)

Not blocking, take or leave: close <app> also goes through closeAndroidApp, so on ADB-backed Android (local and Limrun) a lingering process now makes close fail after about 4 s with android-app-close-timeout. One line in the close and open --relaunch docs, and "ADB-backed Android" in the PR body, would cover it. The test at app-lifecycle-open.test.ts:207 asserts exactly 40 pidof calls, which ties it to the poll interval; asserting at least one call for the process phase and none for the foreground phase would be sturdier.

CI is green and there are no conflicts. The exact-head Android live job covers the confirmed-close relaunch path; the refusal branch is covered by unit tests. The PR can merge after its stacked base #3560.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 13, 2026

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

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant