A warm pool is safe if pre-started runners are idle, hold no credentials, and each takes exactly one job before deletion. It becomes unsafe the moment a warm runner is reused for a second job or is given secrets before a job is assigned. Warm means pre-started, not reused.
The trade
Starting a fresh container or virtual machine for each job adds wait time. A warm pool hides that by keeping a few runners ready. Teams adopt it for speed, then sometimes slide into reusing runners to save more time. That slide is where the security is lost.
GitHub's guidance explains why one job per runner matters. It recommends ephemeral runners for autoscaling because GitHub can guarantee a runner receives only one job, and it warns that re-using hardware to host JIT runners can expose information from the environment, so automation should provide a clean environment each time.
What a warm runner must not hold
- Repository code or tokens from any project
- Cloud credentials
- Cached files from earlier jobs
- A long lived registration to the CI platform
If a warm runner is already registered with broad scope, any job could be assigned to it, including one you did not intend.
Safe design in steps
- Pre-start the compute, not the identity. Boot the machine or container, but register the runner for a job only when work arrives.
- Use a single use registration so the runner cannot take a second job.
- Inject credentials after assignment, and only the ones that job needs.
- Delete the runner and its disk after the job, even on failure.
- Keep warm runners in a network segment with no access to internal systems until assigned.
- Cap pool size and age, and replace idle runners regularly so they do not run an old image.
Signs of a leak
Watch for: a runner ID appearing on two job records, files from one job visible in another, or a job starting faster than a fresh boot would allow because it reused a process. Add a check to the job start that confirms the workspace is empty and fails if not.
Image freshness
A warm runner pre-started last week uses last week's image. Tie pool refresh to your image rebuild so idle runners are cycled when a new image is released. See how often to rebuild runner images.
Quick check
Is a warm pool still ephemeral?
Yes, if every runner is used once and deleted. If runners are reused, it is a persistent pool with a different name.
How big should the pool be?
Size it to typical concurrent demand, and accept some cold starts at peaks. A large idle pool costs more and ages faster.
Sources
- GitHub Docs: Autoscaling with self-hosted runners
- GitHub Docs: Security hardening for GitHub Actions
Last reviewed: 19 October 2026.