Documentation menu
Getting started
Dashboard & analytics
- Dashboard at a glance
- Cost analysis
- Pipeline performance
- Optimization insights
- Knowledge Base
- Connect your AI agent
Managed runners
- Runners overview
- Run your first job
- Migrate from GitHub-hosted
- Latchkey CLI
- The Runners page
- Custom runners (AI Scan)
- Self-healing
- Runner image & software
- Provisioning & warm pools
- Limits & concurrency
Caching
Team & notifications
Billing & plans
Help
How provisioning works
What happens between a job queueing on GitHub and a runner picking it up: warm pools, cold starts with just-in-time registration, the concurrency ceiling, and why a job might wait.
When GitHub queues a job with a latchkey-* label, a webhook tells Latchkey immediately, and Latchkey makes one decision: hand the job to a runner that is already warm, or launch a fresh machine for it. You never see that decision, but it is the difference between a job that starts almost at once and one that waits for a machine to boot, and it explains most of what this page covers.
latchkey-* labelWarm pickup vs cold start#
A warm pickup means a pre-provisioned runner was already online for your workspace: the job is handed straight to it and starts in about two seconds. A cold start means no warm runner fit, so a fresh machine boots just for that job, registers with GitHub using a just-in-time, single-use configuration, and then runs your steps. Both paths end identically: the runner takes exactly one job and the machine is terminated when the job finishes.
Warm pools#
| Plan | Warm pool |
|---|---|
| Developer | One warm latchkey-small runner plus parked capacity |
| Launch | The same baseline: one warm runner plus parked capacity |
| Scale | The same baseline: one warm runner plus parked capacity |
Every plan gets the same warm baseline today, and it covers latchkey-small only: one always-on warm runner backed by parked machines that resume on demand. Anything beyond that, and any job asking for a larger size, cold-starts. Warm capacity costs you nothing while it idles; billing is per job minute, and an idle warm runner is not running a job.
Ephemeral, always#
Provisioning never reuses a machine. Warm or cold, a runner takes exactly one job and is terminated afterward, along with its disk, which is why runner names in the GitHub run view change on every run and why anything a job writes to local disk is gone when the job ends. The security side of this design, just-in-time credentials, private networking, and encrypted single-use disks, is covered in Security architecture.
Concurrency#
A workspace runs up to 40 concurrently busy runners to start (the Launch number; Developer 20, Scale 80 after a plan change; Enterprise limits are set by contract), and only busy runners count: idle warm runners never consume a slot, so warm capacity does not compete with your real jobs. When a burst needs more than your number at once, the overflow jobs stay queued until a slot frees, then start on their own; nothing errors and nothing is lost. On GitHub Actions, a warm runner that was already idle when the limit was reached still takes the one job it was waiting for, so a workspace can have at most one job above its number running for each such runner; Latchkey never stops a runner to enforce the number. If your peaks queue regularly, higher limits are available, contact support.
Hard boundaries#
- Jobs are capped at 4 hours; the machine is terminated at 4 hours even if the job is still running.
- Runners are Ubuntu 24.04 LTS on x86_64 only: no Windows, macOS, arm64, or GPU hosts.
- Runners run in AWS us-east-1.
The full table, including disk sizes and per-plan custom configuration counts, is in Limits and concurrency.
Why a job might sit in queued#
When provisioning cannot or will not launch a runner, there is no error on GitHub's side; the job just waits. That is by design: jobs targeting latchkey-* labels stay queued on GitHub rather than erroring. The usual causes:
- A label typo: no configuration matches the label, so nothing ever picks the job up.
- The repository is not monitored, or the runner configuration is disabled.
- A billing or trial block: an expired trial, a lapsed subscription, or an exhausted free tier on a card-less trial. A "Managed runner blocked" notification fires when this happens, at most once per day.
- A custom runner's image is still building; jobs targeting its label wait until the build completes.
- The workspace is at its busy-runner ceiling (40 to start, the Launch number); the job starts as soon as a slot frees.
The symptom-by-symptom walkthrough, with how to confirm each cause, is in Troubleshooting.
Why is my job sitting in queued?
Either it is waiting for capacity beyond the warm baseline, in which case a fresh machine is cold-starting for it, or something is blocking it: the label, monitoring, configuration, billing, an in-progress image build, or the concurrency ceiling. Check them in that order.
What is the difference between a warm pickup and a cold start?
A warm pickup hands the job to a machine that is already running, in about two seconds when the runner is already online. A cold start provisions a new machine with just-in-time registration and takes longer. Every plan gets the same warm baseline today: one always-on warm runner plus parked capacity on latchkey-small.
Do idle warm runners count against my concurrency limit?
No. Your workspace's concurrent-runner number (40 to start) counts busy runners only, so warm capacity waiting for work never consumes the limit you are paying attention to.