feat: add iOS device and iOS Simulator drivers - #1148
Draft
kirkbrauer wants to merge 5 commits into
Draft
kirkbrauer wants to merge 5 commits into
kirkbrauer wants to merge 5 commits into
Conversation
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
kirkbrauer
force-pushed
the
feat/ios-devices
branch
from
September 27, 2026 17:21
4c29f71 to
4a164ec
Compare
kirkbrauer
changed the base branch from
main
to
fix/forward-stream-transport-failure
September 27, 2026 17:21
kirkbrauer
added this pull request to stack #1149
September 27, 2026 17:21
kirkbrauer
force-pushed
the
feat/ios-devices
branch
2 times, most recently
from
September 27, 2026 18:17
b9ce161 to
0916f39
Compare
mangelajo
force-pushed
the
feat/ios-devices
branch
2 times, most recently
from
September 28, 2026 19:05
a446b9a to
ab5cac9
Compare
Base automatically changed from
fix/forward-stream-transport-failure
to
main
September 28, 2026 20:09
github-merge-queue Bot
pushed a commit
that referenced
this pull request
Sep 28, 2026
…1146) Part of a stack: #1144 → **#1146** → #1148. This change does not depend on #1144; it is ordered after it so the iOS drivers in #1148 build on both. ## Issue `forward_stream` pumps each direction independently. When the transport under one direction fails, for example a lost router stream or a broken write, only that pump stops. The other keeps waiting on a peer that will never send, so the forwarded connection stays half-open and the client hangs instead of seeing a disconnect it can recover from. This showed up with long-lived tool connections (usbmux, lockdown) forwarded through `j … serve` when the router stream dropped. ## Change `copy_stream` now returns whether it ended at EOF or on a transport failure. On failure, `forward_stream` cancels both pumps, closing both peers. A clean EOF still half-closes only its own direction, so a response can still follow a request's EOF. A failed half-close (`send_eof()` raising `OSError`) also counts as a transport failure, except `ENOTCONN`, which Darwin reports for a normal half-close (#444). ## Impact - Applies to every forwarded stream: exporter session streams, the port-forward and noVNC adapters, `connect_router_stream`, and resource transfers. - On a transport failure, code running inside `async with forward_stream(...)` is now cancelled too, and the block then exits normally because its task group absorbs the cancellation. In-tree callers are unaffected: the exporter session only waits for the RPC to end, the adapters' bodies are empty, and the resource helpers run the caller's work in a separate thread through the blocking portal. Async code that does real work inside the block, for example with `resource_async`, should not treat reaching the end of the block as success. ## Tests - New `streams/common_test.py` (9 tests): router loss through a real gRPC router closes the nested socket and a new stream still works, with and without metrics and with a waiting body; a failed write closes the other direction while its reader is idle; a response after a request's half-close is preserved; parent cancellation closes both idle peers; a failed EOF write closes the other direction, while `ENOTCONN` still lets a response through. - `jumpstarter` package suite: 1013 passed, 20 xpassed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
Add jumpstarter-driver-iosdevice, which exposes one leased iPhone or iPad to standard iOS tools: - A device-scoped usbmux endpoint for tools such as pymobiledevice3, go-ios and libimobiledevice (`j ios serve`, `j ios -- COMMAND`), and usbfluxd registration with `j ios connect`. - Exporter-owned trust: the pairing record stays on the exporter. Clients get a disposable lease identity, and the exporter terminates lockdown and service TLS on both sides. Lease holders otherwise have full device access, including the iOS 17.4+ CoreDevice tunnel. - Configured raw port forwards, and verified HTTPS to an on-device HTTP service such as WebDriverAgent for Appium. USB transport matches only a device's USB entry, so Wi-Fi sync, which makes usbmuxd list the device once per link, does not break it. Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
…ider Add jumpstarter-driver-iossimulator, which runs one simulator per lease in a private device set on a macOS exporter: - Power control: power off keeps the lease's data. A failed first boot removes the partial simulator, and a failed restart only stops it. - An idb transport through an exporter-owned idb_companion. - An optional HTTPS provider selected by exporter configuration. The included AppiumProvider exposes a policy-limited native WebDriver API that works with Appium-Python-Client. It keeps standard W3C error codes and needs Node.js 22, because Appium's HTTPS server fails on Node.js 24 and later. - Leases left behind by a crashed exporter are cleaned up by the next lease or session reset. Each lease directory holds a lock for its owner's lifetime, so live leases are never touched. Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
Link the iOS device and iOS simulator pages to their package READMEs, as for the other drivers, and list them in the driver index. Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
The iOS drivers use UDID, Apple's Unique Device Identifier, and BUID, usbmuxd's system identifier, throughout. typos flags them as misspellings of UUID and BUILD. Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
The detach-warning and Appium startup tests read log output through caplog, which stayed empty on the Linux CI runner even though the warnings were emitted. They now patch the logger that emits them and assert the call directly. Also clear ty's findings in the iOS tests: narrow driver children and optional values instead of reaching through the base types, patch methods instead of assigning to them, and raise AssertionError instead of calling pytest.fail, whose type ty cannot resolve. Assisted-by: Claude:claude-opus-5.5 Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
kirkbrauer
force-pushed
the
feat/ios-devices
branch
from
September 28, 2026 20:09
ab5cac9 to
d15d26e
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds two drivers that let lease holders use standard iOS tools (pymobiledevice3, go-ios, libimobiledevice, idb, Appium) against a leased iOS target:
jumpstarter-driver-iosdevice(physical iPhone/iPad): a device-scoped usbmux endpoint (j ios serve,j ios -- COMMAND, usbfluxd viaj ios connect), configured raw port forwards, and verified HTTPS to an on-device HTTP service such as WebDriverAgent (WDA).jumpstarter-driver-iossimulator(macOS exporter): one simulator per lease in a private device set, power control, an idb transport through an exporter-ownedidb_companion, and an optional exporter-configured HTTPS provider. The includedAppiumProviderexposes a policy-limited native WebDriver API.Both share one client (
IosDeviceClient):info,serve,connect,forward,https,run.Why
Jumpstarter has Android coverage (ADB, emulator) but no way to lease iOS devices or simulators. These drivers expose the transports existing iOS tooling already speaks, rather than wrapping tool-specific actions.
Reviewer notes
trust_mode: exporter, the default): the device pairing record never leaves the exporter. Clients get a disposable, lease-scoped pairing identity, and the exporter terminates lockdown and service TLS on both sides (trust.py). This protects the exporter's pairing identity, not device capabilities: a lease grants full device access, including the iOS 17.4+ CoreDevice tunnel, by design.forward_portsare conveniences, not an access boundary.passthroughmode shares the real pairing record and is opt-in.flockfor its owner's lifetime, so leases left by a crashed exporter are cleaned up at the next lease or session reset, and live leases are never touched.No such module: http_parser, even though Appium declares support for them. The provider reports the detected Node.js version when startup fails.WebDriverWaitwork, while messages and stack traces stay exporter-side.session not createdand leaves the service usable; uncertain session outcomes require a power cycle.{doc}roles because MyST resolves relative.mdlinks through the symlink's real path.Stack
This PR is the top of a stack: #1144 → #1146 → #1148. Only the three iOS commits are new here.
forward_streamcloses both directions when a forwarded transport fails (split out of this work).Testing
jumpstarter-driver-iosdevice287 passed,jumpstarter-driver-iossimulator203 passed. Both also pass on Linux (Python 3.14). Ruff is clean, and ty reports no findings in source files. The strict docs build (make docs,-W -n) passes.uv.lockconsistent (uv lock --check).j ios connect, and Appium with Appium-Python-Client overios.https()to WDA launched through the lease.AppiumProviderwith Appium-Python-Client (20/20 common operations, a refused session, and the Node.js version error).🤖 Generated with Claude Code