Blolabel
← Notes
analysis 8 min read

A reboot ends an unattended iPhone session: after a cold boot iOS withholds developer services until a person enters the passcode

Lockdown answered. devicectl said unavailable. WebDriverAgent could not start, so the passcode stored on the Mac could not be typed by the one process that types it. Our runner's repair routine now stops one rung short of a reboot, on purpose.

On 9 September 2026 a reboot cleared a wedged test session on our benchmark iPhone and ended the unattended run in the same move. After the cold boot the phone answered on USB, reported "PasswordProtected": true, and withheld the developer services that devicectl and WebDriverAgent need, so the passcode stored on the Mac could not be typed by the one process that types it. From that day our rule has been written into the runner: a reboot ends the session, and the automated repair path is not allowed to use one.

Abstract technical illustration of a closed loop of conduits around a sealed core, with one gap in the loop that only an outside hand can close

A wedge that only a reboot cleared, and a reboot that needed a person

The fault came first. A device-side test session stopped answering with DTX capability handshake timed out and zero runner processes on the phone. Nothing sent over the wire cleared it. A reboot did. The cause, as far as we could reconstruct it, was memory pressure on the Mac killing a half-open test session, which left the phone holding the device side of a conversation whose other end no longer existed (FINDINGS.md section 24).

The reboot then produced the state that matters more. A freshly booted iPhone with a passcode keeps most user data under a key that is not available until the passcode has been entered once. Apple's Platform Security guide calls that class Protected Until First User Authentication and makes it the default for third-party app data. Forensic tools call the state BFU. On our iPhone 17 Pro Max on iOS 26.6 the practical reading was three lines:

lockdown:        answering, "PasswordProtected": true
devicectl:       unavailable        <- CoreDevice / developer services withheld
WebDriverAgent:  cannot start, so it cannot type the passcode it has

That is a loop with no exit on the Mac side. Typing the passcode is what WebDriverAgent does when a run finds the phone locked, and WebDriverAgent is the thing that will not start. We read it as a security property working as designed.

Two dates since then widen the blast radius. On 14 September 2026 Apple released iOS 27 and, for phones staying on 26, iOS 26.7 with more than 80 security fixes, 75 of them shared with iOS 27 (MacRumors, 14 September 2026). Installing either one is a reboot. And since iOS 18.1 a phone that has sat locked for 72 hours restarts itself into the same state, a timer the Secure Enclave keeps and the kernel acts on, as reverse engineered by Jiska Classen and reported by heise on 20 November 2024.

iOS Inactivity Reboot Timelapse, @jiskac, 17 November 2024. Jiska Classen's recording of an iPhone left locked restarting itself after the inactivity timer, the same state a rig phone reaches if it is allowed to lock and sit for 72 hours.
Why the Mac cannot bring the phone back after a cold boot three ways to get a cold boot manual reboot to clear a DTX wedge iOS 27 or 26.7 install (14 Sep 2026) 72 h locked: inactivity reboot BFU after boot passcode never entered lockdown: PasswordProtected true devicectl: unavailable WebDriverAgent: cannot start passcode stored on the Mac only WebDriverAgent could type it loop has no exit from the Mac exit: a person enters the passcode on the phone once. Measured on an iPhone 17 Pro Max, iOS 26.6, 9 September 2026. Sources: FINDINGS.md s.24; Apple Platform Security, Data Protection classes; heise, 20 Nov 2024; MacRumors, 14 Sep 2026.
Each box on the right was read from the phone after the reboot. The loop closes only on the phone's own screen.

What answers after a cold boot, and what that implies

layer or eventreadingconsequence for an unattended rigsource
lockdown over usbmuxanswering, "PasswordProtected": truethe phone looks present to any check that only asks whether it is connectedFINDINGS.md s.24
xcrun devicectldevice unavailableno process launch, no process list, no installFINDINGS.md s.24
WebDriverAgent runnercannot startno /status on port 8100, and no way to type the stored passcodeFINDINGS.md s.24
Data Protection class for third-party app dataProtected Until First User Authentication by defaultdata stays keyed to a passcode entry that has not happened yetApple Platform Security guide
Inactivity rebootrestart after 72 hours with no passcode or biometric entry, since iOS 18.1a rig phone left locked over a long weekend reboots itself into this stateheise, 20 Nov 2024 (Classen analysis)
iOS 27 and iOS 26.7both released 14 Sep 2026; 26.7 carries more than 80 security fixesevery install is a reboot, so an automatic overnight update ends the runMacRumors, 14 Sep 2026
Activation wedge (apps stop coming forward)first recorded as reboot-only, later fixed by terminating the runner pidthe reboot was the wrong remedy for that fault; seconds, no personFINDINGS.md s.27 and s.29, 9 Sep 2026
Runner.heal()three steps: type the passcode if locked, restart the forward, recycle the runnerno reboot step, by designphoneshell/bench/runner.py

The seventh row is the one that changed our behaviour most. Section 27 of our findings concluded the same day that app activation degrades under sustained automation and "only a reboot restores it". Section 29 overturned that within hours: the WebDriverAgent process was alive and deaf, devicectl process launch against an already-running app returned success without replacing it, and terminating the runner by pid brought it back on the first attempt. A reboot clears every state at once, which makes it useless as evidence about which state was wrong, and on a phone with a passcode it costs the session besides.

The recovery ladder, and where it stops

Every failure we have seen on the rig sits on one of five rungs. The first four are recoverable from the Mac. The fifth is not, and the runner's repair routine stops above it on purpose: its docstring says it "will NOT" reboot, citing section 24.

Five rungs of repair, and the one that needs a hand on the glass fault remedy from the Mac person? 1. phone locked WebDriverAgent types the stored passcode no 2. port forward dropped restart iproxy on 8100 no 3. runner alive but deaf terminate by pid, clear orphan iproxy, relaunch (seconds) no 4. runner stale after an OS update rebuild, strip, re-sign, reinstall (build up to 1800 s) after rung 5 5. DTX handshake wedge reboot, then BFU: devicectl unavailable yes Runner.heal() in phoneshell/bench/runner.py implements rungs 1 to 3 and stops. Rung 4 is device.build_wda. An OS update is itself a reboot, so rung 4 always arrives with rung 5's cost attached.
The ladder is ordered by cost. Rung 4 is the trap: the rebuild is scriptable, but the update that made it necessary already rebooted the phone.

What it means if you run phone agents overnight

Schedule reboots, never use them as repair. A reboot at the start of a run, with a person there to type the passcode, costs one boot and one passcode entry. A reboot at 03:00 costs every task after it, and those tasks do not fail in an interesting way: they are not scored at all, which in our reporting makes the run shorter and leaves its score untouched. Our runner reports never-attempted tasks as not scored rather than failed, so this shows up as missing rows in the results.

Treat an OS update as a scheduled reboot. With iOS 27 and iOS 26.7 both on offer since 14 September 2026, a rig phone with automatic updates on will take one of them overnight, reboot into BFU, and also come back with a runner built against the previous build. That combines rung 4 and rung 5 of the ladder in a single event. Our judgement: automatic updates should be off on a benchmark phone, and updates should be a planned step with a person present and a rebuild immediately after.

Never leave the rig phone locked. The 72 hour inactivity reboot only counts time since the passcode or Face ID was last used, and our prep step sets Auto-Lock to Never before a run for exactly this family of reasons. That setting once failed to stick silently on this device, and the phone locked mid-run, which is why the prep step now checks for the checkmark in the chosen row rather than for the word Never on the page.

Guard the cause. The wedge that forced this reboot came from the Mac rather than the phone: a memory-starved Mac killed a half-open test session. A long device job should not share a machine with anything that can exhaust memory, and two jobs should not share a phone; ours take an exclusive flock in phoneshell/lock.py after a second process once drove the phone alongside a sweep for seventy minutes and sixty-four rows had to be discarded.

The option we have not tested: our findings file names a passcode-less test device as "the real answer" for anyone running this seriously, because it could be rebooted freely. We have not run that configuration, so treat it as a hypothesis. It also trades away the data protection that makes the phone a realistic user's phone.

Check it yourself

You need a passcode-protected iPhone with Developer Mode on, a Mac with Xcode, and a willingness to reboot the phone. Read the state before and after.

# before the reboot: the phone is trusted, developer services are up
xcrun devicectl list devices
pymobiledevice3 lockdown info | grep -i -E 'PasswordProtected|ProductVersion|BuildVersion'
curl -s -m 3 localhost:8100/status | head -c 200

# reboot from the phone (or from the Mac), do not touch the screen afterwards, then:
xcrun devicectl list devices                       # expect the device listed as unavailable
pymobiledevice3 lockdown info | grep -i PasswordProtected   # still answers: true
xcrun devicectl device info processes --device <udid>  # expect a failure, not a list

# enter the passcode on the phone once, then repeat: everything comes back

# our code: the repair routine and its refusal to reboot
grep -n "def heal" -A 20 phoneshell/bench/runner.py
sed -n '/^## 24\./,/^## 25\./p' FINDINGS.md
sed -n '/^## 29\./,/^## 30\./p' FINDINGS.md

For the 72 hour timer you need a locked phone and three days, or Jiska Classen's write-up via the heise report linked below. For the update dates, Apple's release notes for iOS 26.7 and the MacRumors report of 14 September 2026.

What would prove this wrong

Sources

  1. phoneshell, FINDINGS.md sections 24, 27 and 29 (9 September 2026), and phoneshell/bench/runner.py Runner.heal. https://github.com/Blomega/phoneshell/blob/main/FINDINGS.md
  2. Apple, Apple Platform Security: Data Protection classes (Protected Until First User Authentication). https://support.apple.com/guide/security/data-protection-classes-secb010e978a/web
  3. Ben Schwan, heise online, Reboot after inactivity: How Apple's new iOS security feature works, 20 November 2024 (analysis by Jiska Classen). https://www.heise.de/en/news/Reboot-after-inactivity-How-Apple-s-new-iOS-security-feature-works-10077069.html
  4. Jiska Classen, Reverse engineering iOS 18 inactivity reboot, naehrdine blog, November 2024. https://naehrdine.blogspot.com/2024/11/reverse-engineering-ios-18-inactivity.html
  5. MacRumors, iOS 18 security feature causes iPhone to reboot after three days of inactivity, 14 November 2024. https://www.macrumors.com/2024/11/14/ios-18-3-day-iphone-reboot/
  6. Juli Clover, MacRumors, Apple releases iOS 26.7 with security fixes, 14 September 2026. https://www.macrumors.com/2026/09/14/apple-releases-ios-26-7/
  7. Appium WebDriverAgent, v16.12.8 released 11 September 2026 (npm registry). https://registry.npmjs.org/appium-webdriveragent