Pair Button + CFNetwork Crash Analysis¶
Date: 2026-04-12 (CFNetwork part RESOLVED, Pair button part still relevant) Status: CFNetwork crash fixed. Pair button mechanism described below is the path forward but needs to be implemented via the right protocol layer.
Part 1: CFNetwork Crash — RESOLVED¶
Original problem¶
CDS crashed with SIGSEGV in HTTPConnectionCache::timeoutIdleConnections() when
service connection actions ran. NULL+0x68 deref inside CFNetwork.
Root cause¶
interpose_xpc_remote_connection_create_with_remote_service was returning NULL
for any service not in our hardcoded list of 10 services. CFNetwork registered
NULL in HTTPConnectionCache and crashed during the idle-cleanup timer.
Fix applied¶
Parse all 74 services from the RSD Handshake Services dict into g_services[]
at init time. The interpose then has a real port for any service Xcode asks for,
and never returns NULL. See iosmux_xpc_proxy.m Handshake event handler.
This part of the document is kept for historical context only — the bug is gone.
Part 2: Pair Button Mechanism — STILL RELEVANT¶
Problem¶
Xcode shows "Pair" button even when DeviceInfo.pairingState is set to .paired. Clicking it triggers an XPC action flow that we currently can't satisfy.
Mechanism (verified by DVTCoreDeviceCore disassembly)¶
Xcode UI displays the device based on a computed deviceWindowCategory property
in DVTDeviceKit, which depends on three KVO properties of DVTCoreDevice_Impl:
deviceWindowCategory =
if hasConnection then connected_path (category 3 or 6 → no Pair button)
else disconnected_path (category 0 or 4 → shows Pair button)
hasConnection is computed:
_shadowUseAssertion is a CoreDevice.DeviceUsageAssertion? field on
DVTCoreDevice_Impl. It is set when Xcode's local
CoreDevice.RemoteDevice.acquireDeviceUsageAssertion(withReason:options:...)
returns success — which dispatches an AcquireDeviceUsageAssertionActionDeclaration
through Mercury to CDS and waits for the reply. (Wire action ID:
com.apple.coredevice.action.acquireusageassertion. The Swift type name
includes "Device" — earlier notes said "AcquireB…" because Swift mangling
uses B as a single-letter substitution for the previously seen
identifier Device, which decodes to "AcquireDeviceUsageAssertion".)
Without a successful response to that action, _shadowUseAssertion stays nil →
hasConnection is false → device window category is "disconnected" → Xcode shows
the Pair button.
Implication¶
To make the Pair button disappear, we need CDS to respond successfully to the
acquireusageassertion action. The exact response format is the
CoreDevice.AcquireDeviceUsageAssertionActionDeclaration.Output associated
type — and per the empirical disasm in
notes/iosmux-mercury-action-design-research.md (Q2), no concrete .Output
struct exists in CoreDevice; the Codable Output is Void (empty). The
serialized return container that DVT cares about is
CoreDevice.UsageAssertionInformation (UUID identifier + owningProcess +
processName + creationReason + hostName + preparednessDescription), but it's
returned by the listUsageAssertions query, not by Acquire itself; on
Acquire success Xcode just constructs a local DeviceUsageAssertion
instance from the inputs it sent.
Two gates, not one¶
hasConnection (computed from _shadowUseAssertion.fulfilled) and
pairingState (a DeviceInfo struct field) are independent gates that
look like one. The disasm above shows hasConnection controls whether the
Pair button is rendered at all; once a device row exists in Xcode's
Devices window, pairingState drives the row's label and icon.
Older project notes (e.g. archive/2026-03-session8-five-questions.md)
that say "Pair button gates on pairingState" are describing label-state
observation without separating it from button rendering. The DVTCoreDeviceCore
disasm in this document is the empirical anchor for button rendering;
reconcile any older mention of "pair-state gates the button" against it.
ADR-0009 §Consequences
names a successful synthetic AcquireDeviceUsageAssertion reply (Mercury Codable
Output) as the mechanism that flips _shadowUseAssertion.fulfilled and
thereby hides the Pair button without invoking iOS Pair-Setup against
the real iPhone.
Empirical confirmation 2026-04-28 (session 14 iter 17)
Pair-button click GAMBIT iteration loop (commits b808eae …
49c8f1e) reached the milestone state where Apple's Codable
decoder accepts the entire PairActionDeclaration.Output =
DeviceConnectionChangeResult reply with pairingState: Paired
and Pairing attempt completed with error nil. The Pair button
did NOT disappear; instead Xcode UI transitioned to a new
spinner state ("Xcode has already started pairing… Follow the
instructions on iPhone (iosmux) to complete pairing.").
DVTFoundation's log at the same instant:
unknown: skipping implicit preparation for device being ignored
with reason: deviceIsUnpaired. So even after PairAction returns
success with pairingState: Paired, Xcode UI still considers the
device unpaired for connection-preparation purposes. This
confirms empirically that _shadowUseAssertion.fulfilled (this
section's "Pair button gating") is the third independent gate,
AND that there is at minimum a fourth gate — the
"implicit preparation" decision — that PairAction success does
not by itself satisfy.
Full schema + iteration trail in
gambit-pair-action-schema.md.
Open question for the next-gate research is Q-D66-14 in
d66-research-questions.md.
Action interception complication¶
Xcode↔CDS uses Mercury Codable XPCDictionary with mangledTypeName envelope, NOT
the flat CoreDevice.featureIdentifier dict format that pymobiledevice3 uses
against the iPhone. Filtering by a top-level string key is impossible on this
connection. See action-interception-full-picture.md for the synthesis and the
new strategy options.
Part 3: Missing DeviceInfo Fields — UNVERIFIED HYPOTHESIS¶
We currently set: name, productType, hardwareModel, marketingName, state, pairingState, visibilityClass, areDeveloperDiskImageServicesAvailable, preparednessState.
The following fields may also affect Xcode's device categorization. The list
comes from disassembling DVTCoreDeviceCore.DVTCoreDevice_Impl setters and
matching them to CoreDeviceProtocols.DeviceInfo fields. None of these have
been runtime-verified to actually change Xcode behavior.
| Field | Plausible value | Why it might matter |
|---|---|---|
| transportType | .localNetwork | Auto-pair + connection logic |
| platform | .iOS | UI classification |
| deviceType | .iPhone | UI icon/category |
| reality | .physical | vs virtual/simulated |
| osVersion | from Handshake | UI display |
| osBuild | from Handshake | Compatibility checks |
| udid | from Handshake | Identity cross-check |
| authenticationType | .manualPairing | Pairing flow type |
| developerModeStatus | .enabled | Skip developer mode prompt |
| bootState | .booted | Device is running |
| isMobileDeviceOnly | true | iOS device flag |
All of OSVersion / BuildVersion / UniqueDeviceID / ProductType / SerialNumber / DeviceClass / HardwarePlatform / ChipID / BoardId / UniqueChipID are available in the RSD Handshake Properties dict (46 keys total).
When to set these¶
Defer until after action interception is working. If acquireusageassertion
returning success is enough to make the Pair button disappear and CDS to consider
the device usable, the additional DeviceInfo fields may not be needed. If it
isn't enough, set them one at a time and observe.