From 879b2125e1c330ce02ff89583ef8fa264fb0f8fa Mon Sep 17 00:00:00 2001 From: Bryan Dent Date: Mon, 13 Jul 2026 10:45:30 -0400 Subject: [PATCH] fix(cli-platform-apple): don't re-pass -CurrentDeviceUDID to a booted simulator `runOnSimulator` called `open Simulator.app --args -CurrentDeviceUDID ` unconditionally, before the `simulator.state !== 'Booted'` guard. For an already-booted device this makes Simulator.app re-issue a boot request that CoreSimulator rejects with "Unable to boot device in current state: Booted", surfaced as a modal alert on every subsequent `run-ios` re-launch. Only pass `-CurrentDeviceUDID` when the device is not already booted; for a booted device just bring the running Simulator.app to the foreground. Co-Authored-By: Claude Opus 4.8 --- .../__tests__/runOnSimulator.test.ts | 31 ++++++++++++++++--- .../src/commands/runCommand/runOnSimulator.ts | 21 ++++++++----- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/packages/cli-platform-apple/src/commands/runCommand/__tests__/runOnSimulator.test.ts b/packages/cli-platform-apple/src/commands/runCommand/__tests__/runOnSimulator.test.ts index 596de693b..110176b3b 100644 --- a/packages/cli-platform-apple/src/commands/runCommand/__tests__/runOnSimulator.test.ts +++ b/packages/cli-platform-apple/src/commands/runCommand/__tests__/runOnSimulator.test.ts @@ -37,11 +37,30 @@ beforeEach(() => { ); }); -test('opens Simulator.app with the device UDID when it exists', async () => { +test('opens Simulator.app with the device UDID when it is not already booted', async () => { (fs.existsSync as jest.Mock).mockImplementation((target) => String(target).endsWith('Simulator.app'), ); + await runOnSimulator(xcodeProject, 'ios', 'Debug', 'TestApp', args, { + ...simulator, + state: 'Shutdown', + }); + + expect(child_process.execFileSync).toHaveBeenCalledWith('open', [ + `${developerDir}/Applications/Simulator.app`, + '--args', + '-CurrentDeviceUDID', + simulator.udid, + ]); +}); + +test('opens Simulator.app without -CurrentDeviceUDID when the device is already booted', async () => { + (fs.existsSync as jest.Mock).mockImplementation((target) => + String(target).endsWith('Simulator.app'), + ); + + // `simulator` is already in the `Booted` state. await runOnSimulator( xcodeProject, 'ios', @@ -51,12 +70,16 @@ test('opens Simulator.app with the device UDID when it exists', async () => { simulator, ); + // Bring the already-running Simulator.app to the foreground without re-issuing + // a boot for the booted device, which would otherwise trigger the + // "Unable to boot device in current state: Booted" modal alert. expect(child_process.execFileSync).toHaveBeenCalledWith('open', [ `${developerDir}/Applications/Simulator.app`, - '--args', - '-CurrentDeviceUDID', - simulator.udid, ]); + expect(child_process.execFileSync).not.toHaveBeenCalledWith( + 'open', + expect.arrayContaining(['-CurrentDeviceUDID']), + ); }); test('falls back to DeviceHub via devices:// deep link when Simulator.app is absent', async () => { diff --git a/packages/cli-platform-apple/src/commands/runCommand/runOnSimulator.ts b/packages/cli-platform-apple/src/commands/runCommand/runOnSimulator.ts index 093ed6a4c..6854038c2 100644 --- a/packages/cli-platform-apple/src/commands/runCommand/runOnSimulator.ts +++ b/packages/cli-platform-apple/src/commands/runCommand/runOnSimulator.ts @@ -27,8 +27,13 @@ export async function runOnSimulator( * that the Simulator.app is running. * * We also pass it `-CurrentDeviceUDID` so that when we launch it for the first time, - * it will not boot the "default" device, but the one we set. If the app is already running, - * this flag has no effect. + * it will not boot the "default" device, but the one we set. + * + * We only pass that flag when the target device is not already booted. Passing + * `-CurrentDeviceUDID` for an already-booted device makes Simulator.app re-issue a + * boot request, which CoreSimulator rejects with "Unable to boot device in current + * state: Booted" and surfaces as a modal alert on every subsequent run. When the + * device is already booted we just bring the running Simulator.app to the foreground. */ const activeDeveloperDir = child_process .execFileSync('xcode-select', ['-p'], {encoding: 'utf8'}) @@ -51,12 +56,12 @@ export async function runOnSimulator( ); if (fs.existsSync(simulatorApp)) { - child_process.execFileSync('open', [ - simulatorApp, - '--args', - '-CurrentDeviceUDID', - simulator.udid, - ]); + child_process.execFileSync( + 'open', + simulator.state === 'Booted' + ? [simulatorApp] + : [simulatorApp, '--args', '-CurrentDeviceUDID', simulator.udid], + ); } else if (fs.existsSync(deviceHubApp)) { child_process.execFileSync('open', [ `devices://device/open?id=${simulator.udid}`,