xcuitest driver 12.14.1 stops trusting an empty tunnel list; phoneshell still reads a failed usbmux call as an unplugged cable
Three xcuitest driver releases landed on 3 October 2026. The one that matters for real iPhones is 12.14.1: a tunnel registry that answered with an empty list used to win over a usbmux lookup that could see the phone. phoneshell does not run Appium, but its own bring-up makes the same mistake with usbmux, and tells the user to plug in a phone that is plugged in.
appium-xcuitest-driver 12.14.1, published to npm on 3 October 2026 at 18:15 UTC, stops a reachable but empty RemoteXPC tunnel registry from overruling the usbmux and devicectl device list. Up to 12.13.3, once every tunnel had dropped, each new real-device session failed with "Unknown device or simulator UDID" while the phone sat on the cable, visible to usbmux (issue #2986, opened 22 September). The fix, PR #2991, returns the union of both lists.
Three driver releases on 3 October, one of them about real devices
| Version | npm publish (UTC) | Change | Pull request, merged (UTC) | Real devices affected | Source |
|---|---|---|---|---|---|
| 12.13.3 | 28 Sep 14:49 | bump WebDriverAgent for package creation with Xcode 26+ | #2996 | build only | CHANGELOG.md |
| 12.14.0 | 3 Oct 05:47 | support simulatorPasteboardAutomaticSync and the connect-hardware preference for Device Hub, by updating dependencies | #2999, 05:42 | no (simulator) | CHANGELOG.md, PR #2999 |
| 12.14.1 | 3 Oct 18:15 | merge tunnel and legacy device discovery | #2991, 18:08 (opened 23 Sep) | yes: discovery on iOS 17+ tunnels | CHANGELOG.md, PR #2991 |
| 12.15.0 | 3 Oct 21:44 | support launching WebDriverAgent on the iPhone Duo simulator (issue #2997, Xcode 27.1 beta, preinstalled WDA) | #3000, 21:36 | no (simulator) | CHANGELOG.md, PR #3000 |
The bug report is precise. getConnectedDevices() in lib/device/connected-devices-client.ts runs the tunnel registry lookup and the legacy lookup in parallel with Promise.allSettled, then returned the tunnel result whenever that promise was fulfilled. A registry whose HTTP endpoint answers but lists zero tunnels is fulfilled with an empty array. That empty array went to device-discovery.ts, which concluded the UDID was not a real device and tried the simulator path, which threw. The reporter, from a CI device farm, put the consequence plainly: running the documented long-lived tunnel-creation process without reconnect retries was "strictly worse than not running it at all once a single tunnel drops", because before a registry existed the same devices resolved through the legacy path.
The diff between the 12.13.3 and 12.15.0 npm tarballs shows the replacement. Tunnel UDIDs come first, then legacy-only UDIDs, de-duplicated case-insensitively with the registry's spelling kept. If one lookup fails, the driver warns and uses the other, including an empty result; it throws only when both fail, with both causes attached. The release also requires appium-ios-device ^3.1.24 to exclude a bplist-parser@0.5.0 import failure. The PR reports focused tests passing 15 of 15 and 15 of 18 functional CI jobs passing. The three failures (server startup on 26.4, a nested-iframe web test, Settings navigation on 16.4) passed in the nearest upstream run, and the author does not claim a macOS or iPhone hardware comparison.
What changes for anyone running real iPhones through Appium
If you run appium driver run xcuitest tunnel-creation as a daemon, upgrade to 12.14.1 or set --disconnect-retry-*; on 12.13.3 and earlier a single dropped tunnel turns every subsequent session into a hard failure with a misleading error. After upgrading, set the udid capability on every real-device session. The PR says so itself: detectUdid is unchanged and selects the final UDID in the returned list, so with devices in both sources and no UDID given, a legacy-only device can now be picked where a tunnel device used to be. On a desk with two phones that is the wrong phone. The error message you used to see is also worth unlearning: on these versions "Unknown device or simulator UDID" did not mean the UDID was wrong.
phoneshell makes the same mistake with usbmux
phoneshell does not use Appium; it builds, launches and forwards WebDriverAgent itself. Its device discovery in phoneshell/bringup.py merges two lists the way 12.14.1 now does: scan() takes pymobiledevice3 usbmux list first, then adds devices from xcrun devicectl list devices. The flaw is one layer down. usbmux_devices() in phoneshell/device.py returns an empty list when the pymobiledevice3 binary is missing or its output is not JSON, so a failed lookup and an empty desk look identical. Then diagnose() counts a phone as on the cable only if it came from usbmux. Its docstring says "no phone in usbmux -> the cable, always".
We reproduced the consequence on 3 October 2026 by stubbing both lookups with the same UDID. With usbmux listing the phone, diagnose() returns state no_tunnel, "The phone is here but nothing is listening", which is right. With usbmux returning an empty list and devicectl reporting the phone as wired and connected, it returns no_device, "No iPhone on the cable", with the fix "Plug the phone in with a cable that carries data". The phone is plugged in. pymobiledevice3==11.8.0 is in requirements.txt, so this needs an install that skipped it or a version whose output changed; it is a latent fault, not one we have seen on the rig.
The fix has the same two parts as PR #2991: make usbmux_devices() return None on failure so the caller can tell "could not ask" from "asked, nothing there", and treat a devicectl entry with a wired transport and connected=True as on the cable when usbmux could not be asked. The rule underneath both bugs: an empty list from a lookup is a measurement only if the lookup is known to have worked.
Check it yourself
- Publish times:
curl -s https://registry.npmjs.org/appium-xcuitest-driver | python3 -c "import json,sys;t=json.load(sys.stdin)['time'];[print(v,t[v]) for v in ['12.13.3','12.14.0','12.14.1','12.15.0']]" - The code change:
npm pack appium-xcuitest-driver@12.13.3 appium-xcuitest-driver@12.15.0, extract both withtar xzf, anddifftheirpackage/build/lib/device/connected-devices-client.js. - The report and the fix:
https://github.com/appium/appium-xcuitest-driver/issues/2986and/pull/2991. - phoneshell:
grep -n "def usbmux_devices" -A10 phoneshell/device.py;grep -n "cabled = " -B3 -A3 phoneshell/bringup.py. To reproduce, replacephoneshell.device.usbmux_deviceswith a function returning[]andphoneshell.device.list_deviceswith one returning aDevice(..., transport="wired", connected=True)for your configured UDID, then callphoneshell.bringup.diagnose(); the state isno_device.
What would prove this wrong
If a session on appium-xcuitest-driver 12.14.1 or later, with a reachable registry that lists no tunnels and the device visible to usbmux, still fails with "Unknown device or simulator UDID", the fix did not cover the case it was written for. We expect no such report in the driver's issue tracker by 31 December 2026. For phoneshell, by the same date the stubbed reproduction above should return a state other than no_device; if it still says the cable is out, the fix described here was not made.
Sources
- maksymalferov-b2s, Real-device discovery treats a reachable-but-empty tunnel registry the same as a fully working one, appium-xcuitest-driver issue #2986, 22 September 2026
- dlowzzxx, fix: merge tunnel and legacy device discovery, PR #2991, opened 23 September, merged 3 October 2026
- npm registry metadata for appium-xcuitest-driver: publish times for 12.13.3, 12.14.0, 12.14.1, 12.15.0 (read 3 October 2026)
- KazuCocoa, Device Hub preferences, PR #2999, merged 3 October 2026
- KazuCocoa, support launching WDA on Duo, PR #3000, merged 3 October 2026; issue #2997 (29 September 2026)
- appium-ios-device PR #246, plist import compatibility fix required via ^3.1.24
- Blolabel, earlier note on UDID case handling across iOS tools
- phoneshell source: phoneshell/device.py, phoneshell/bringup.py, requirements.txt (read 3 October 2026)