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. The stack is #10052, then #10087, then #10090, then this one, then #10163, which adds the upload workflow. Nothing in the first three needs it.

Five commits, in dependency order.

Commit Lines What it does
1. Evidence in the description 397 added, 100 removed Makes attach write a run's video and screenshots into the pull request description, in one block per platform that a later attach replaces. In #10087 it posts a comment.
2. The remote session 2,266 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.
3. The choice of backend 247 added, 80 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. It also tells the tests' support code how to reach a borrowed device. Outside the unit tests, commits 1 and 3 and small edits to src/fixture.ts, src/platform/android/emulator.ts, and src/core/ in commit 2 hold every edit to code that #10087 and #10090 added.
4. Unit tests 2,386 added, 7 removed Tests of the borrowed-device code. They run with no device, no network, and no key.
5. Docs 166 added, 17 removed Explains to an agent how a borrowed-device session works, what it costs, and how to end it.

Commit 1 has its own unit tests. The unit tests for commits 2 and 3 arrive in commit 4.

Everything under integration/expo-native/src/core/ and integration/expo-native/specs/support/, 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 every shared file with its hash, 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.
  • When the machine's gh cannot attach files, attach hands the video and screenshots to the session's runner through the tunnel, and the runner keeps them as the artifact verify-evidence.
  • 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 repository or environment secret. Its one permissions block is contents: read, so no job can write to the repository. The step that reads the request uses the job's own read-only token to check the requested commit. A session starts only for the commit the run was dispatched on, or for a later commit of that branch.

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. Apart from attach, 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, an empty changeset, 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 9, 2026 5:01am UTC
swingset Ready Ready Preview Oct 9, 2026 5:01am 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
  • Autofix · 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: ea5fc27

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-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-borrowed-device branch from a8d3a72 to 888ca43 Compare October 8, 2026 04:37
@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-borrowed-device branch from 82c1950 to e30aa43 Compare October 8, 2026 14:19
}

function bearer(req: IncomingMessage): string | null {
return /^Bearer\s+(.+)$/i.exec((req.headers.authorization ?? '').trim())?.[1] ?? null;
@pkg-pr-new

pkg-pr-new Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@10131

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@10131

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@10131

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@10131

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@10131

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@10131

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@10131

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@10131

@clerk/expo-biometrics

npm i https://pkg.pr.new/@clerk/expo-biometrics@10131

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@10131

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@10131

@clerk/express

npm i https://pkg.pr.new/@clerk/express@10131

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@10131

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@10131

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@10131

@clerk/mosaic

npm i https://pkg.pr.new/@clerk/mosaic@10131

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@10131

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@10131

@clerk/react

npm i https://pkg.pr.new/@clerk/react@10131

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@10131

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@10131

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@10131

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@10131

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@10131

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@10131

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@10131

commit: ea5fc27

mikepitre and others added 5 commits October 9, 2026 00:51
`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 — ea5fc278 Deployed Oct 9, 2026 by vercel[bot]
Preview – clerk-js-sandbox — ea5fc278 Deployed Oct 9, 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.

2 participants