Skip to content

feat(#202): Phase 4 — eradicate legacy runner apps (uninstall, not just pkill)#270

Merged
Lykhoyda merged 13 commits into
mainfrom
feat/202-phase4-6-rethink
Jun 11, 2026
Merged

feat(#202): Phase 4 — eradicate legacy runner apps (uninstall, not just pkill)#270
Lykhoyda merged 13 commits into
mainfrom
feat/202-phase4-6-rethink

Conversation

@Lykhoyda

Copy link
Copy Markdown
Owner

Summary

#202 Phase 4 (first phase of the re-think spec docs/superpowers/specs/2026-06-10-device-control-phase4-6-rethink-design.md §1): at iOS device-open, ensureSingleRunner now detects the legacy upstream runner apps installed on the target simulator (com.callstack.agentdevice.runner + .uitests.xctrunner) and simctl uninstalls them.

Closes out the #202 comment (2026-06-08): killing the host processes (Phase 1) was insufficient — iOS relaunches an installed XCUITest runner into the foreground mid-maestro_run, backgrounding the app under test and wedging CDP. Only uninstalling fixes it; eradication must target artifacts, not processes.

Design decisions (multi-LLM plan review, 2 rounds)

  • No memo — scan every device-open (one simctl listapps, ~150–350 ms measured). A memo can't be made safe: the Phase 1.5 device lock fails open in its degraded path, so another session can reinstall on the same UDID.
  • No terminate stepsimctl uninstall terminates a running app itself; Phase 1's scopedKill already ran.
  • Zero-apps parse guard — a successful listapps parsing to 0 bundles is a parse/format failure (a booted sim always has system apps), surfaced as a warning, never read as "clean".
  • Fail-open — every failure becomes a warning (removedApps + meta.timings_ms.appEradication in the result); a device-open is never blocked. Opt-out: RN_DEVICE_KILL_LEGACY=0.

Final-review catch

The pre-existing eradication/fast-runner gates used raw args.platform === 'ios', silently skipping when platform is omitted (defaults to iOS). Fixed to the normalized platform + wiring test (94b0932).

Live gate (booted iPhone 17 Pro, iOS 26.5)

Planted a stub app under the legacy bundle id (RnFastRunner.app clone, PlistBuddy-rewritten CFBundleIdentifier), then ran the device-open path:

parser baseline: 29 apps parsed from real listapps <udid> output
planted com.callstack.agentdevice.runner on CE9D3DB9-FB86-4D73-A09C-CD7A51A953DB
{
  "removedApps": ["com.callstack.agentdevice.runner"],
  "timings": { "scopedKill": 161, "appEradication": 1762, "fileCleanup": 1 }
}
GATE PASS: legacy app eradicated at device-open path

Steady-state appEradication (nothing to remove): ~150–350 ms.

Tests

  • 1913/1913 unit tests green (8 new orchestrator/selector tests, 3 wiring tests, mandatory baseDeps update in the pre-existing suite, normalized-platform wiring test)
  • docs-site build green; CLAUDE.md + architecture.mdx + changeset updated

Phases 5 (#264 supervisor split) and 6 (#186 foreign-flow arbitration) follow on this spec.

🤖 Generated with Claude Code

Lykhoyda and others added 13 commits June 10, 2026 22:59
…arbitrate)

Re-examines the #202 architecture debate with post-shipping evidence:
- Phase 4: uninstall (not just pkill) the on-device legacy runner apps
- Phase 5: supervisor split so the bridge survives Metro restarts (#264)
- Phase 6: canonical maestro surface + arbiter-aware foreign flows (#186)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- BLOCKER: mandatory baseDeps update in pre-existing suite (TypeError is
  swallowed by fail-open, contingency could never trigger)
- zero-apps parse guard: clean:false on 0 parsed bundles, never memoized
- selectInstalledLegacyApps takes the parsed Set (one parse per scan)
- spec: corrected stale cdp_repair_action caller claim

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… memo, no terminate)

- BLOCKER: live gate detects via parseSimctlListapps (raw .includes also
  matches nested GroupContainers keys — gate validated the wrong layer)
- memo dropped: device lock's degraded fail-open path can't rule out a
  reinstall by another session; scan every open (~tens of ms)
- terminateApp dropped: simctl uninstall terminates running apps itself
- gate also proves the parser reads real 'listapps <udid>' output

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…listapps

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… bundles, fail-open

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ry open)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t device-open

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ate)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…form

An open with platform omitted defaults to iOS but raw args.platform === 'ios'
read false, silently skipping legacy-app eradication (the Phase 4 feature)
and the fast-runner spawn. Found by final pre-PR review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@Lykhoyda Lykhoyda merged commit 73c6bf4 into main Jun 11, 2026
10 checks passed
@Lykhoyda Lykhoyda deleted the feat/202-phase4-6-rethink branch June 11, 2026 08:55
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