Skip to content

fix(ios): correctly implement runner fallback for physical device screenshots#132

Merged
thymikee merged 2 commits into
callstack:mainfrom
nickdima:fix/ios-device-screenshot-xcode26
Feb 25, 2026
Merged

fix(ios): correctly implement runner fallback for physical device screenshots#132
thymikee merged 2 commits into
callstack:mainfrom
nickdima:fix/ios-device-screenshot-xcode26

Conversation

@nickdima
Copy link
Copy Markdown
Contributor

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 calls write(to: URL(fileURLWithPath: requestedOutPath)) on the device — resulting in a permissions error since /tmp is 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 using xcrun devicectl device copy from — the same mechanism already used by the recording feature.

Bonus: Passes appBundleId to 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

  • Swift runner — writes to NSTemporaryDirectory() and returns tmp/<filename>; activates target app via appBundleId before capturing
  • apps.ts — after runner returns the device path, copies the file to the host via devicectl device copy from (iterating runner container bundle IDs, same as recording); threads appBundleId through the call
  • interactors.ts / dispatch.ts — threads appBundleId to screenshot() so the active session's app is always in frame
  • runner-client.ts — exports IOS_RUNNER_CONTAINER_BUNDLE_IDS to avoid duplication between apps.ts and record-trace.ts

Test plan

nickdima and others added 2 commits February 25, 2026 15:50
`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+
@thymikee thymikee merged commit 43aae1d into callstack:main Feb 25, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix in #130 broken on physical iOS device: runner receives Mac path and can't write to it

2 participants