# Migrate from GitHub-hosted runners

> The full migration guide: what actually changes, the three migration paths, the exact label mapping the Migrate Runners tool applies, what the PRs contain, verification, and rollback.

Source: https://latchkey.dev/documentation/migrate-from-github-hosted

## Summary

- Only the `runs-on:` label changes; actions, secrets, environments, and caches on GitHub's side keep working untouched.
- Three paths: the **Migrate Runners** tool (one PR per repository, up to 20 monitored repos per pass), a one-line hand edit, or AI Scan for a right-sized image.
- The tool maps standard Ubuntu labels to `latchkey-small` and never touches Windows or macOS jobs, unrecognized labels, or `${{ }}` expressions.
- Rollback is one revert: undo the migration PR and jobs route back to GitHub-hosted immediately.

Migrating to Latchkey runners is a routing change, not a rewrite. The `runs-on:` label is the only thing that decides which runner takes a job, so it is the only thing a migration touches. Everything else keeps working exactly as before: the actions you call, the secrets GitHub injects, your environments and protection rules, and caches on GitHub's side. And GitHub-hosted labels keep working alongside `latchkey-*` labels, so nothing forces a full cutover.

## What changes, and what keeps working

## Before you start

**Prerequisites**
- [ ] The Latchkey GitHub App is installed on my organization
- [ ] The repositories I want to migrate are monitored in Latchkey
- [ ] My trial or subscription is active

## Three ways to migrate

The three paths compose: most teams hand-edit one workflow as a pilot, bulk-migrate with the tool, and reach for AI Scan on the handful of repositories that deserve their own image.

| Path | Best for |
| --- | --- |
| **Migrate Runners** tool | Bulk migration: up to 20 monitored repositories per pass, one reviewable PR each |
| Hand-editing `runs-on:` | A single workflow, a pilot job, or labels the tool does not recognize |
| [AI Scan](/documentation/custom-runners) | Repositories that deserve a right-sized custom image rather than a preset |

### Path 1: the Migrate Runners tool

The tool has two entry points, and both open the same modal: the **Migrate Runners** item in the dashboard sidebar, and the CTA in the Runner Types card footer on the [Runners page](/documentation/runners-dashboard). Opening migration PRs requires an owner or admin role.

Nothing in a repository changes until your team merges its pull request. If a repository already has an open migration PR, the tool links to it instead of opening a duplicate; if there is nothing left to switch, it shows a **Nothing to migrate** state. With more than 20 repositories to move, migrate in passes.

### The exact mapping the tool applies

| In your workflow | What the PR does |
| --- | --- |
| `ubuntu-latest`, `ubuntu-24.04`, `ubuntu-22.04` | Switched to `latchkey-small`, the lowest-cost size |
| Other Linux size specs | Switched to the smallest Latchkey size that meets their CPU and memory |
| Windows and macOS jobs | Left untouched |
| Self-hosted labels the tool does not recognize | Left untouched |
| Any line containing a `${{ }}` expression, such as `${{ matrix.os }}` | Left untouched |

The untouched rows are deliberate: jobs the tool cannot confidently route keep running exactly where they run today, so you can migrate your Linux fleet now and leave mixed-OS workflows safely intact. Anything the tool skips can still be moved by hand whenever you are ready.

### What each pull request contains

Each PR rewrites the `runs-on:` lines and nothing else; as the PR body itself puts it, "Only runs-on: lines were touched. Every other line in each file is byte-identical." The one exception: when the migration also injects Latchkey cache steps, those steps are the only additions beyond the `runs-on` lines, and the body says so. A before and after mapping table for each file shows exactly what changed, so review takes minutes, not days.

### Path 2: hand-edit a single workflow

For one workflow the change is one line per job, and both label forms route identically:

The step-by-step version of this path, including verifying the first run and picking a size, is [Run your first job](/documentation/run-your-first-job).

### Path 3: AI Scan, for repos that deserve their own image

Some repositories are worth more than a label swap. **AI Scan** reads the workflows a repository actually runs and proposes a custom runner configuration with a right-sized shape and an image that preinstalls the repo's toolchain, so jobs skip their setup steps entirely. If that sounds like one of yours, start at [Custom runners with AI Scan](/documentation/custom-runners) and migrate that repository to its custom label instead of a preset.

## An incremental strategy

You do not need a big-bang migration, and we suggest avoiding one. Start with one low-risk repository, ideally a flaky or slow one where cheaper minutes, faster pickup, and self-healing are most visible, and let it run for a week before you commit the fleet.

Jobs that need GPU, Windows, macOS, or arm64 hosts stay on GitHub-hosted or other runners, and that is fine indefinitely: `runs-on` is decided per job, so a workflow can mix Latchkey and GitHub-hosted jobs freely.

## Verify after merging

1. **Check where the job ran** In the GitHub run view, the job's runner name confirms a Latchkey machine took it rather than a GitHub-hosted one. Expect a fresh name on every run: runners are single-use by design.
2. **Watch the minutes land** Migrated jobs' minutes accrue against your plan's free tier. The Billing modal shows free-tier progress and estimated cost so far, and the [Cost Analysis](/documentation/cost-analysis) page tracks Latchkey runner spend alongside your GitHub spend; details in [Runner usage and free minutes](/documentation/runner-usage-and-free-minutes).

## Rolling back

Rollback is the same one-line change in reverse: revert the migration PR and jobs route back to GitHub-hosted runners immediately. There is nothing else to undo, no agent to uninstall and no configuration to clean up, and the repository stays monitored, so your analytics continue either way.

## Common questions

### Do I need to change my secrets?

No. GitHub injects your Actions secrets into the job at runtime on Latchkey runners exactly as it does on GitHub-hosted ones. Nothing is reconfigured, and Latchkey never reads their values.

### Does actions/cache keep working?

Yes, unchanged. For faster saves and restores on Latchkey runners, the Latchkey cache action (`latchkey-dev/cache-action@v1`) is a drop-in upgrade; see [Dependency caching](/documentation/dependency-caching).

### Can I mix Latchkey and GitHub-hosted runners?

Yes, freely and indefinitely. `runs-on` is decided per job, so each job in a workflow can run wherever it fits best.

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
