fix(desktop/perm-06): re-pass automation port on non-prod Quit & Reopen relaunch#9391
Conversation
…en relaunch AppState.restartApp() relaunches via a bare `open <bundle>` (no argv, no env), so after the permission-flow Quit & Reopen the reopened app resolved its automation port from the launchd-inherited OMI_AUTOMATION_PORT (or default 47777) instead of the port the harness launched with. The harness, polling the pre-quit port, timed out and could not prove signed-in/onboarded continuity (PERM-06 FAIL, Codex Wave 18). Re-pass --automation-port=<current port> as an argv on the NON-PROD relaunch; argv is the highest-precedence port source, so it beats any inherited env. The production relaunch stays a byte-identical bare `open`. Extracted a pure relaunchCommand(appPath:isNonProduction:automationPort:) seam so the behavior is unit-testable (restartApp() itself terminates the host and can't be unit-run). An earlier persist-last-bound-port attempt was reverted: the launchd session carried OMI_AUTOMATION_PORT=47812 and env outranks a persisted value, so the bare-open relaunch shadowed the persisted port — disproven at runtime. Tests: RestartRelaunchCommandTests (non-prod re-passes the port; prod bare open; delay precedes open; flag == bridge portPrefix). Full Desktop suite green (the sole failure is the already-CI-skipped ActionItemsFTSRepairTests, a macOS-26 system-SQLite issue). Runtime: with launchd OMI_AUTOMATION_PORT=47812 set, launched on argv 47894 → quit_and_reopen → reopened process argv shows --automation-port=47894, binds 47894, 47812 gets no listener, session intact. INV-UI-1: adds no purple. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Pull request overview
This PR fixes a non-production desktop automation harness reliability issue in the “Quit & Reopen” permission flow by ensuring the app relaunch preserves the originally bound automation port. It introduces a testable relaunch-command builder and adds unit tests to prevent regressions, while documenting the correct e2e verification loop.
Changes:
- Update
AppState.restartApp()to build its relaunch shell command via a pure helper that (non-prod only) re-passes--automation-port=<port>as argv. - Add
RestartRelaunchCommandTeststo assert non-prod relaunch includes the port argv while prod remains byte-identical to the prior bareopenform. - Update the e2e SKILL documentation to reflect same-port relaunch behavior and common pitfalls when polling after Quit & Reopen.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| desktop/macos/e2e/SKILL.md | Updates PERM-06 recipe to keep polling the original automation port and avoid false pass/fail pitfalls after relaunch. |
| desktop/macos/Desktop/Tests/RestartRelaunchCommandTests.swift | Adds focused unit tests for the relaunch command to lock in the non-prod argv re-pass and prod byte-identical behavior. |
| desktop/macos/Desktop/Sources/AppState/AppState+SystemActions.swift | Implements relaunchCommand(...) helper and uses it in restartApp() to re-pass automation port on non-prod relaunch. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
kodjima33
left a comment
There was a problem hiding this comment.
Desktop bug fix: relaunch re-passes automation port on non-prod builds; pure relaunchCommand + test, prod path byte-identical. Owner-override.
What
The non-prod automation bridge exposes a
quit_and_reopenaction (PERM-06) thatdrives the real permission-flow restart,
AppState.restartApp(). That relaunch is abare
/bin/sh -c "sleep 0.5 && open \"<bundle>\""— no argv, no env. The reopenedapp therefore resolves its automation port from whatever
OMI_AUTOMATION_PORTthelaunchd user session happens to carry (or the default 47777), not the port the
harness launched with (e.g. 47894 via
--automation-port=). A harness polling theoriginal port sees no listener after Quit & Reopen,
wait-readytimes out, andsame-bundle signed-in / onboarded continuity can't be proven.
Fix
AppState.restartApp()now builds its relaunch command via a purerelaunchCommand(appPath:isNonProduction:automationPort:). On non-prod builds itre-passes the current automation port as an argv:
argv is the highest-precedence port source in the bridge's resolver, so the reopened
bundle rebinds the same port the harness used — overriding any launchd-inherited
OMI_AUTOMATION_PORT. The production relaunch is untouched:relaunchCommandreturns the byte-identical
sleep 0.5 && open "<path>"forisNonProduction == false.Why this shape (an earlier persist-based attempt was wrong)
I first tried persisting the last-bound port in UserDefaults and falling back to it in
the bridge's port resolver. Runtime testing disproved it: the launchd session here
carried
OMI_AUTOMATION_PORT=47812(set out-of-band — no script doeslaunchctl setenv), and env outranks a persisted value, so the bare-openrelaunch inherited47812 and shadowed the persisted 47894. Only re-passing the port as argv — which
outranks the inherited env — reliably pins the reopened port. That approach was fully
reverted; this is the task's Option A.
Tests
RestartRelaunchCommandTests(new, 4): non-prod re-passes--automation-port=<port>;prod is byte-identical bare
open(no automation args); thesleepprecedes theopen; the flag equals the bridge's ownportPrefix(DRY, no drift). Pure helper —fails if the fix is reverted. (
restartApp()itself can't be unit-run — itterminates the host — so the pure-command builder is the testability seam.)
ActionItemsFTSRepairTests/testRepairToleratesMissingActionItemsFTSShadowTable,already in
scripts/swift-test-skips.json(macOS-26 system SQLite rejectsDELETE FROM sqlite_master) and skipped in CI — unrelated.Verification (exercised, not just compiled)
Named bundle
omi-perm06v, with the launchd session deliberately left carrying adifferent port (
OMI_AUTOMATION_PORT=47812) to prove the override:run.sh --yolo OMI_AUTOMATION_PORT=47894→open --args --automation-port=47894; bridge on 47894, gate-0/statesignedIn+onboarded.quit_and_reopen→{restarting:true, relaunch_path:/Applications/omi-perm06v.app}.…/Omi Computer --automation-port=47894— the re-pass, visible inps.opencode would have bound 47812./state(47894): same bundle, signedIn=true, onboarded=true, bridgePort=47894 — session intact on the same port.Invariants
Touches
desktop/macos/Desktop/Sources/**→ INV-UI-1 (no purple): adds zero purpleliterals; brand ratchet unaffected.
Scope
Non-prod automation-bridge / dev-harness only; no user-facing behavior →
no-changelog-needed.Does not claim the PERM-06 acceptance row PASS — post-merge runtime re-verification is the Codex lane.