fix(ios): correctly implement runner fallback for physical device screenshots#132
Merged
thymikee merged 2 commits intoFeb 25, 2026
Merged
Conversation
`xcrun devicectl device screenshot` was removed in Xcode 26.x, causing `agent-device screenshot` to fail on physical iOS devices with exit code 64. This fix routes physical device screenshots through the AgentDeviceRunner XCTest runner (already used for snapshot/interaction commands) instead: - Add `screenshot` command to the Swift runner's `CommandType` enum - Implement the handler using `XCUIScreen.main.screenshot()`, writing a timestamped PNG to the app's temp directory (returned as `tmp/<file>`) - If `appBundleId` is provided, activate the target app before capturing so the screenshot shows the app under test rather than the runner itself - In `screenshotIos()`, pass `appBundleId` to the runner command then pull the file back to the host via `xcrun devicectl device copy from` (same mechanism used by the recording feature) - Thread `appBundleId` through `Interactor.screenshot()` → `dispatch.ts` → `screenshotIos()` so the active session's app is always captured - Extract `IOS_RUNNER_CONTAINER_BUNDLE_IDS` to `runner-client.ts` so it can be shared between `apps.ts` and `record-trace.ts` Fixes: screenshots failing with "Failed to capture iOS screenshot" on devices using Xcode 26.x / devicectl 506.6+
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
Fixes #131 — the runner fallback introduced in #130 is broken on physical iOS devices because the Swift runner receives the Mac output path and tries to write to it on the device (where it doesn't exist in the app sandbox).
Root cause:
runIosRunnerCommand(device, { command: 'screenshot', outPath })passes the host path (e.g./tmp/screenshot.png) to the Swift runner, which then callswrite(to: URL(fileURLWithPath: requestedOutPath))on the device — resulting in a permissions error since/tmpis not writable in an iOS app sandbox.Fix: The Swift runner writes to
NSTemporaryDirectory()(which IS writable) and returns the relative device path (tmp/<filename>). The TypeScript daemon then pulls the file to the host usingxcrun devicectl device copy from— the same mechanism already used by the recording feature.Bonus: Passes
appBundleIdto the runner so it can activate the target app before capturing, ensuring the screenshot shows the actual app under test rather than the AgentDeviceRunner UI.Changes
NSTemporaryDirectory()and returnstmp/<filename>; activates target app viaappBundleIdbefore capturingapps.ts— after runner returns the device path, copies the file to the host viadevicectl device copy from(iterating runner container bundle IDs, same as recording); threadsappBundleIdthrough the callinteractors.ts/dispatch.ts— threadsappBundleIdtoscreenshot()so the active session's app is always in framerunner-client.ts— exportsIOS_RUNNER_CONTAINER_BUNDLE_IDSto avoid duplication betweenapps.tsandrecord-trace.tsTest plan
agent-device screenshotsuccessfully captures Musixmatch (not AgentDeviceRunner) and saves a valid PNG to the hostshouldFallbackToRunnerForIosScreenshot) is preserved for backward compatibility with older Xcode versions