Blolabel
← Notes
event 7 min read

Xcode 27 broke the detached WebDriverAgent launch on older iPhones, and the fix is one launch option

A five-week trail from a beta bug report to appium-xcuitest-driver 12.13.1, the first patch that keyed on the wrong variable, and the one place in our own harness that would stop an overnight run on an upgraded Mac.

Since Xcode 27 shipped on 14 September 2026, a WebDriverAgent runner started with xcrun devicectl device process launch can come up on an older iPhone, sit on screen for about 20 seconds, and exit with XCTest error 10300: "Failed to background test runner within 30.0s." Appium's fix, released in appium-xcuitest-driver 12.13.1 on 20 September, is one launch option, ActivateSuspended: true, sent through the DVT process-control service instead of devicectl. The pymobiledevice3 11.8.0 build on our rig already sends that option for every device on iOS 12 or later.

Abstract technical illustration of a process stuck at a doorway between a bright foreground layer and a darker background layer, with a second path that starts directly in the background

Five weeks from beta report to a one-line fix

The failure was reported against a beta, patched against the wrong variable, reported again two days after the final release, and fixed properly on 20 September. Every entry below names its primary record.

dateeventhost and devicewhat it establishedsource
16 Aug 2026appium/appium#22636 opened by StevenWong12Xcode 27 beta 5, active DDI 27A5237l, iPhone 14 on iOS 26.5.2, Appium 3.6.0, driver 12.3.4devicectl-launched runner fails with code 10300 before its test case starts; a probe without Appium or WDA reproduces itgithub.com/appium/appium/issues/22636
16 Aug 2026same reporter, RemoteXPC pathsameProcessControl launched the runner, port 8100 still refused, same 10300. That launch did not yet pass ActivateSuspendedissue #22636 comments
20 Aug 2026appium-xcuitest-driver PR #2949 merged (mykola-mokhnach)n/adisables the devicectl fallback on iOS and tvOS 27+, keyed on the device's OS versiongithub.com/appium/appium-xcuitest-driver/pull/2949
25 Aug 2026SunflowersLwtech data pointXcode 26.6 (17F113), iPad Pro 11-inch on iPadOS 26.6same 10300 with no Xcode 27 involved; the same host, WDA build and steps work on an iPhone on iOS 26.6issue #22636 comments
14 Sep 2026Xcode 27 (27A266a) and iOS 27.0 (24A437) releasedn/athe beta stack becomes the default toolchain on upgraded Macsdeveloper.apple.com/news/releases
16 Sep 2026appium-xcuitest-driver#2978 opened by SMasekoSTSXcode 27, one device on iOS 26 and one on iOS 18preinstalled WDA stays up about 20 s then closes; session fails at the 60000 ms launch timeout. Neither device runs iOS 27, so the #2949 gate never firesgithub.com/appium/appium-xcuitest-driver/issues/2978
20 Sep 2026PR #2980 (navin772), released as 12.13.1n/aadds extraOptions: {ActivateSuspended: true} to the DVT launch and bumps appium-ios-remotexpc to ^5.20.1github.com/appium/appium-xcuitest-driver/pull/2980

The correction in the middle matters more than the fix at the end. PR #2949 treated this as an iOS 27 problem and keyed the guard on the phone's OS version. Issue #2978 shows it on iOS 26 and iOS 18 devices driven from an Xcode 27 host. The comment the 20 September patch adds to lib/device/wda-host-ops.ts says it directly: with the Xcode 27 DDI and XCTest stack, the synthesized Home press the runner uses to background itself "is ignored on older iOS versions". The variable is the host's developer disk image, not the phone.

Why a foreground launch now dies at the 30 second mark

An XCTest UI-testing runner is an app that has to get out of the way before it can drive other apps. Launched in the foreground, it presses Home on itself and waits for the system to confirm it is in the background. Under the Xcode 27 stack on older iOS, per the patch comment, that press is ignored, the wait runs out at 30.0 seconds, and the process exits with code 1 before WebDriverAgent binds port 8100. devicectl device process launch always starts an app in the foreground and has no flag to change that. Xcode and DVT process control can start the runner already in the background, which skips the step that fails.

Same runner, two launch paths, one step that the Xcode 27 stack breaks devicectl device process launch runner starts in the FOREGROUND synthesized Home press to background itself ignored under the Xcode 27 DDI on older iOS 30.0 s wait, error 10300, exit 1 port 8100 never opens Appium gives up at 60000 ms; reporter saw the app for about 20 s DVT process control, ActivateSuspended: true runner starts in the BACKGROUND no Home press needed, the failing step is skipped testmanagerd session handshake, as Xcode does WebDriverAgent answers GET /status on 8100 appium-xcuitest-driver 12.13.1, 20 Sep 2026: adds ActivateSuspended to the DVT launch pymobiledevice3 11.8.0 (our rig): sets ActivateSuspended for product major version 12 and up devicectl has no flag for a background launch, so its path cannot be patched from outside
The failing step exists only on the foreground path. Option names are from the PR #2980 diff and from pymobiledevice3's services/dvt/testmanaged/xcuitest.py.

The error text itself is old. appium/appium#11702, opened on 19 November 2018, carries the same "Failed to background test runner within 30.0s" from a real-device xcodebuild run. What is new in 2026 is a named trigger that several reporters reproduce on demand.

One caveat keeps this from being tidy. The 25 August data point reproduced 10300 on Xcode 26.6 with an iPad on iPadOS 26.6, while the same host worked with an iPhone. So the Xcode 27 stack is the common trigger in the reports, not the only one. We have one iPhone and no iPad on the rig, so we cannot say what separates the iPad case.

Where our own harness would break

phoneshell launches the runner the way Appium used to: devicectl first, with --terminate-existing, and a DVT session through pymobiledevice3 developer dvt xcuitest as the fallback. We read every call site on 21 September. Three of the four recover on an Xcode 27 host. The one that does not is the one an unattended benchmark depends on.

phoneshell's four launch sites against an Xcode 27 host call site first try then outcome cli.py phoneshell up (auto) devicectl 40 s for /status, then DVT recovers bringup.py Bridge.connect devicectl 40 s, then DVT, then 45 s recovers bringup.py supervisor loop devicectl DVT only if one was already held it depends 2 misses, 5 s apart, max 2 relaunches device.py recycle_runner called by bench/runner.py heal() devicectl 40 s, then gives up no fallback A failed heal raises RigUnavailable: the task is not scored rather than failed, which is honest, but an overnight run on an upgraded Mac would score nothing after the first runner death.
Read from phoneshell/cli.py, bringup.py, device.py and bench/runner.py at commit cac38a6. The wait lengths are the loop bounds in that code.

Our code also carries the same mislabel Appium corrected. The fix hint in launch_wda reads "this is the iOS 27 failure mode for the devicectl launch path", and the docstrings on both fallbacks say the devicectl route is "reported broken on iOS 27". Anyone who reads that and checks the phone's version, sees 26.6.2, and moves on, is looking at the wrong machine.

What to do on a rig that has upgraded, or is about to

Treat the Xcode version as part of the instrument. Our rig runs Xcode 26.6 (17F113) against an iPhone 17 Pro Max on iOS 26.6.2 (build 23G90), checked on 21 September. No published phoneshell number was measured under Xcode 27. We have not reproduced the Xcode 27 failure because we have not upgraded, and this note does not claim we have. The reports are from other people's hardware.

If you drive preinstalled WDA through Appium, 12.13.1 or later is the version that works on an Xcode 27 host with pre-27 phones, and it needs appium-ios-remotexpc ^5.20.1 underneath it plus a working RemoteXPC tunnel. Without the tunnel the driver has no DVT route, and PR #2949 means it will not quietly fall back to devicectl on iOS 27 devices.

If you launch the runner yourself, make the DVT launch with ActivateSuspended the first try rather than the fallback. The cost is real: a DVT session is tethered, so the runner dies when the host process does, where a devicectl launch survives the terminal closing. That trade used to favour devicectl. On an Xcode 27 host it no longer exists, because the detached launch does not come up. Our judgement is that detached-first is now the wrong default for any harness that runs unattended.

Watch the error you check for. devicectl exits 0 whether or not the runner survives its own bootstrap, which our cli.py already notes, so the 10300 text shows up in the device log, not in the launch result. A harness that waits for /status is the only reliable detector, and it costs the full timeout every time.

Check it yourself

# the fix and its rationale, in Appium's own words
gh pr diff 2980 -R appium/appium-xcuitest-driver | sed -n '1,40p'
gh issue view 2978 -R appium/appium-xcuitest-driver
npm view appium-xcuitest-driver time --json | grep '"12.13'

# what pymobiledevice3 sends on a DVT launch (look for ActivateSuspended)
P=$(python3 -c "import pymobiledevice3,os;print(os.path.dirname(pymobiledevice3.__file__))")
grep -n "ActivateSuspended\|StartSuspendedKey" $P/services/dvt/testmanaged/xcuitest.py

# which toolchain your rig is actually on
xcodebuild -version
xcrun devicectl list devices

# phoneshell's launch sites and the one without a fallback
git clone https://github.com/Blomega/phoneshell && cd phoneshell
grep -n "launch_wda_dvt\|launch_wda(\|recycle_runner" phoneshell/*.py phoneshell/bench/*.py

What would prove this wrong

The claim is that the host's Xcode 27 stack, not the phone's iOS version, decides whether a devicectl-launched runner survives, and that a background launch through DVT avoids it. Two observations would break it. If Xcode 27.1 (in beta since 18 September as 27A9269) restores the devicectl path on iOS 26 phones, the problem was a regression, not a design change, and detached launches are safe again. If an Xcode 27 host with appium-xcuitest-driver 12.13.1 and a working tunnel still logs 10300 against an iOS 26 iPhone, ActivateSuspended is not sufficient. We will upgrade a second Mac to Xcode 27 by 31 October 2026, run phoneshell's 80 tasks against the same iPhone through both launch paths, and publish the launch success counts for each.

Sources

  1. StevenWong12, usePreinstalledWDA fails on Xcode 27 because the devicectl-launched XCTest Runner cannot enter background, appium/appium#22636, opened 16 August 2026, with comments by KazuCocoa, mykola-mokhnach and SunflowersLwtech (25 and 26 August).
  2. mykola-mokhnach, fix: disable devicectl launch fallback for preinstalled WDA on iOS/tvOS 27+, appium-xcuitest-driver PR #2949, merged 20 August 2026.
  3. SMasekoSTS, Preinstalled WebDriverAgent terminates shortly after launch after upgrading to Xcode 27, appium-xcuitest-driver#2978, opened 16 September 2026, closed 20 September 2026.
  4. navin772, fix(opts): pass ActivateSuspended: true to host opts for backgrounding WDA, PR #2980, released as v12.13.1 on 20 September 2026.
  5. app/, Failed to install WebDriverAgentRunner on Real Device, appium/appium#11702, 19 November 2018: the same message nearly eight years earlier.
  6. Apple, Developer releases: Xcode 27 (27A266a) and iOS 27.0 (24A437) on 14 September 2026; iOS 26.6.2 (23G90) on 8 September 2026; Xcode 27.1 beta (27A9269) on 18 September 2026.
  7. pymobiledevice3 11.8.0, pymobiledevice3/services/dvt/testmanaged/xcuitest.py, _generate_launch_args: ActivateSuspended set for product major version 12 and later. Read on our rig 21 September 2026.
  8. Blomega/phoneshell at commit cac38a6: phoneshell/device.py (launch_wda, launch_wda_dvt, recycle_runner), phoneshell/bringup.py, phoneshell/cli.py, phoneshell/bench/runner.py. github.com/Blomega/phoneshell