Skip to content

test(expo): borrow a device on a CI runner when the machine cannot run one - #10131

Draft
mikepitre wants to merge 5 commits into
mike/expo-verify-ci-device-specsfrom
mike/expo-verify-borrowed-device
Draft

mikepitre wants to merge 5 commits into
mike/expo-verify-ci-device-specsfrom
mike/expo-verify-borrowed-device

Conversation

@mikepitre

@mikepitre mikepitre commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Description

The end-to-end tests in #10087 run on a simulator or an emulator on the machine that runs their CLI, so they need a Mac for iOS and hardware virtualization for Android. An agent in a cloud sandbox often has neither. This PR adds a second backend to that CLI. On a machine that cannot run the device, up starts a session on a GitHub Actions runner, and the CLI drives the device there through a tunnel, with the same verbs, tests, and evidence. No session has been started from the commits in this PR.

The tests and the CLI are the package integration/expo-native/, and the verify-clerk-expo skill is the instructions that point an agent at it. This is the last PR of the stack: #10052, then #10087, then #10090, then this one. Nothing in the other three needs it.

Four commits, in dependency order.

Commit Lines What it does
1. The remote session 1,897 added, 7 removed Adds the workflow that boots a simulator or emulator on a GitHub runner for one session, the code on the runner that drives it, and the code on your machine that talks to it through a tunnel.
2. The choice of backend 165 added, 58 removed Teaches the CLI to choose. It uses a simulator or emulator on this machine when there is one and borrows one on a runner when there is not. These and an edit to src/fixture.ts in commit 1 are the only edits to code that #10087 and #10090 added.
3. Unit tests 1,612 added, 9 removed Tests of the borrowed-device code. They run with no device, no network, and no key.
4. Docs 116 added, 16 removed Explains to an agent how a borrowed-device session works, what it costs, and how to end it.

The unit tests for commits 1 and 2 arrive in commit 3.

Everything under integration/expo-native/src/core/ and most of the unit tests are the same files, byte for byte, in clerk/clerk-ios#661 and clerk/clerk-android#1069. src/core/MANIFEST lists the core files with their hashes, and a unit test fails when one drifts. Review them once.

What changes:

  • --backend remote on doctor, up, and run forces the remote backend. With no flag the CLI picks the local backend where the machine can run the device and the remote one anywhere else, and its first line says which and why. A worktree keeps the backend it started with until down.
  • verify-remote.yml runs one session for either platform. It builds the pushed commit, never the working tree, as a Release app with the JS embedded, so the device needs no Metro. It boots the device, records the video, and ends on down, after 15 idle minutes, or at 60 minutes.
  • --runner <label> on up or run names the runner for a new session. The defaults are macos-26 for iOS and ubuntu-24.04 for Android.
  • With the remote backend, doctor checks that GitHub has HEAD and that this machine reaches GitHub, the tunnel host, and Clerk. It starts nothing without --live. doctor --live starts one short session and stops it.
  • run.json gains remote, with the provider, the runner label, and the commit the session built. It is null for a local run.
  • down ends the runner job. down --stale also ends a session that a crashed run of the same checkout left running.
  • deviceToolCommand, which decides the adb arguments a test may send to a device, moves to remote/protocol.ts, because the session on the runner applies the same rule.
  • The skill gains references/remote.md.
  • The Expo Native Runner Tests job in ci.yml now also runs when verify-remote.yml changes, because a unit test reads that file.

In the package, test/remote-host.test.ts is this repository's own. It also holds the unit tests of the two build products and the fixture build from #10087 and #10090.

To try it, on any machine with Node 24.8 or newer, the team's Clerk Platform API key, and push access to this repository:

$ npm ci --prefix integration/expo-native
$ git push
$ integration/expo-native/bin/control-clerk-expo doctor --platform ios --backend remote
$ integration/expo-native/bin/control-clerk-expo run native-auth-view --platform ios --backend remote
$ integration/expo-native/bin/control-clerk-expo down

On a machine that cannot run the device, leave out --backend remote. On a free runner label up takes tens of minutes, nearly all of it the build.

Someone with write access starts a session, by workflow_dispatch only. The workflow has no pull_request or push trigger and uses no secret. Its one permissions block is contents: read, so no job can write to the repository.

Every route on the tunnel answers 403 without a bearer token that the CLI made. The token stays under integration/expo-native/.verify/remote/ on the machine that started the session. The workflow gets only its SHA-256. The tunnel is a Cloudflare quick tunnel, so Cloudflare can read the traffic, which includes sign-in tickets for test users. The Clerk application is a throwaway one that down deletes, and the runner never gets a secret key.

A session costs nothing on the default labels, which are free for a public repository and slow. --runner blacksmith-6vcpu-macos-26 for iOS or --runner blacksmith-8vcpu-ubuntu-2204 for Android is faster and billed by the minute.

A run on a machine's own device takes the same path as before this PR. The new code runs only when the CLI picks the remote backend, and the workflow in #10090 does not use it. Outside the package and the skill directory, this PR adds verify-remote.yml, one path in ci.yml, one line in .prettierignore, and an edit to the skill's row in .claude/skills/README.md.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other: test tooling

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
clerk-js-sandbox Ready Ready Preview Oct 8, 2026 8:49pm UTC
swingset Ready Ready Preview Oct 8, 2026 8:49pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@changeset-bot

changeset-bot Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4d2e482

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@mikepitre
mikepitre force-pushed the mike/expo-verify-ci-device-specs branch from 1c5dda6 to 857b339 Compare October 8, 2026 00:47
@mikepitre
mikepitre force-pushed the mike/expo-verify-borrowed-device branch from 8df30c1 to 3f84067 Compare October 8, 2026 00:47
@mikepitre
mikepitre force-pushed the mike/expo-verify-borrowed-device branch from 3f84067 to a8d3a72 Compare October 8, 2026 00:50
@mikepitre
mikepitre force-pushed the mike/expo-verify-ci-device-specs branch from 857b339 to 95dcfea Compare October 8, 2026 00:50
@mikepitre
mikepitre force-pushed the mike/expo-verify-borrowed-device branch from a8d3a72 to 888ca43 Compare October 8, 2026 04:37
@mikepitre
mikepitre force-pushed the mike/expo-verify-ci-device-specs branch from 95dcfea to da01c53 Compare October 8, 2026 04:37
@mikepitre
mikepitre force-pushed the mike/expo-verify-ci-device-specs branch from da01c53 to a41dd66 Compare October 8, 2026 12:03
@mikepitre
mikepitre force-pushed the mike/expo-verify-borrowed-device branch from 888ca43 to 82c1950 Compare October 8, 2026 12:03
@mikepitre
mikepitre force-pushed the mike/expo-verify-ci-device-specs branch from a41dd66 to 8a906f0 Compare October 8, 2026 14:19
@mikepitre
mikepitre force-pushed the mike/expo-verify-borrowed-device branch from 82c1950 to e30aa43 Compare October 8, 2026 14:19
@mikepitre
mikepitre force-pushed the mike/expo-verify-ci-device-specs branch from 8a906f0 to ae22325 Compare October 8, 2026 14:39
@mikepitre
mikepitre force-pushed the mike/expo-verify-borrowed-device branch from e30aa43 to d8902ee Compare October 8, 2026 14:39
@mikepitre
mikepitre force-pushed the mike/expo-verify-ci-device-specs branch from ae22325 to 5053f9a Compare October 8, 2026 15:19
@mikepitre
mikepitre force-pushed the mike/expo-verify-borrowed-device branch from d8902ee to ddad046 Compare October 8, 2026 15:19
@mikepitre
mikepitre force-pushed the mike/expo-verify-ci-device-specs branch from 5053f9a to f4c574b Compare October 8, 2026 15:39
@mikepitre
mikepitre force-pushed the mike/expo-verify-borrowed-device branch from ddad046 to 6a7fa08 Compare October 8, 2026 15:39
@mikepitre
mikepitre force-pushed the mike/expo-verify-ci-device-specs branch from f4c574b to 795ca89 Compare October 8, 2026 16:49
@mikepitre
mikepitre force-pushed the mike/expo-verify-borrowed-device branch from 6a7fa08 to dfba920 Compare October 8, 2026 16:49
@mikepitre
mikepitre force-pushed the mike/expo-verify-ci-device-specs branch from 795ca89 to aa119ba Compare October 8, 2026 20:40
@mikepitre
mikepitre force-pushed the mike/expo-verify-borrowed-device branch from dfba920 to c11bb10 Compare October 8, 2026 20:40
mikepitre and others added 5 commits October 8, 2026 16:45
`attach` posted the video and screenshots of a run as a pull request
comment. It now writes them into the description with
`gh pr edit --attach`, in one block between two HTML comments that name
the platform. A later `attach` replaces the block, so the description
holds the latest run. Everything outside the block is passed back as it
was read.

`attach` reads the description again just before it writes. If the
description changed, it rebuilds on the new text once, and fails if it
changed again. It refuses a description whose markers are doubled,
halved, or out of order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

2 active deployments
Preview – swingset — 4d2e4820 Deployed Oct 8, 2026 by vercel[bot]
Preview – clerk-js-sandbox — 4d2e4820 Deployed Oct 8, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant