AI coding

Running Agents on Ephemeral CI Runners Instead of Your Laptop

Antonio Evans9 min read

A laptop sits on a desk, lid open but screen dark and idle.
Key points

Key points

  1. 1An ephemeral runner is a machine built from a pinned image for one job, given scoped credentials and cache access, then destroyed so nothing an agent does survives the job.
  2. 2Worktrees solve file collisions on your laptop but not port conflicts, shared databases, dependency installs or disk growth, so parallel agent work outgrows local isolation.
  3. 3Cache keys decide whether a disposable runner is fast or merely clean: key on lockfile hashes and image digests, never on branch names.
  4. 4Secrets should be short-lived, job-scoped and read-limited, with a separate publishing credential reachable only by the final gate step.

Why your laptop is the wrong host for an agent

A coding agent that can read your repository, run shell commands, install packages and open browser sessions is an execution environment with a language model attached, not a smarter autocomplete as Penligent notes. Robert Brennan's framing of the OpenHands sandbox work is blunt: running an agent with permission prompts skipped on your laptop puts every file, secret and production credential you hold within reach in that talk. Whatever the agent reads, it can also copy into a commit, a log line or an outbound request.

The usual first step is git worktrees. Each worktree is a separate working directory with its own branch that shares the repository's object database, so creating one is nearly instantaneous and file edits stop colliding per this Google Cloud walkthrough. That is a real improvement for text. It is not a boundary for anything else.

A team running a large TypeScript monorepo with PostgreSQL, Redis and ClickHouse found that worktrees broke down on the non-file parts: two worktrees wanted the same dev port, both pointed at the same database so one agent's migration desynchronized the other's schema, every shared service had to be duplicated, and one reported case burned 9.82 GB across two worktrees for a roughly 2 GB codebase because each got its own dependency tree according to Trigger.dev's write-up. Add the sandbox dimension and the picture gets worse: isolation at the software or kernel level is not the same as hardware virtualization, and agents have escaped environments that relied on a network proxy plus a container per Solo.io's sandbox guide.

The decision rule: keep your laptop for editing and reading diffs. Move any agent job that installs dependencies, runs migrations, touches a test database or needs a real credential into a runner that does not exist after the job ends.

ConcernLaptop with worktreesShared long-lived runnerEphemeral runner
File collisionsSolved per worktreeSolved per checkoutSolved per job
Port and database conflictsManual port and DB allocationManual, plus leftoversFresh namespace each job
Credential exposureYour full credential setPrevious job's residueScoped, destroyed with the job
Disk growthGrows per worktreeFills up over weeksBounded by one job
Cache speedInstant, localWarm but driftingNeeds designed cache keys
Trust boundaryEverything you can accessEvery job ever run thereExactly one job

Choose and pin the runner image

Ephemeral runners are not a vendor feature, they are a property of how you register and destroy the machine. The GitHub runner supports an --ephemeral flag under which it accepts a single job, deregisters itself and leaves the host to be destroyed as documented here. GitHub's own reference recommends Actions Runner Controller as the Kubernetes-based autoscaler that handles provisioning, job execution and cleanup, and notes that any self-hosted runner needs Docker installed if your workflows use container actions or service containers per the self-hosted runner reference.

The image is the part that decides whether your builds are reproducible. Pin it by digest, not by tag:

jobs:
  agent:
    runs-on: ubuntu-latest
    container:
      image: ghcr.io/acme/agent-runner@sha256:3f9c1b7e... # digest, not :latest
    timeout-minutes: 30

Rules that matter more than the syntax:

  1. Bake toolchains into the image. OS, language runtime, package managers, git, the agent CLI itself. Every tool installed at job time is a download you pay for on every run and a version you did not pin.
  2. Pin the agent too. An agent CLI that auto-updates turns "works on Monday" into a mystery on Friday. The image digest is your record of what ran.
  3. Set a hard timeout. Ephemeral runners bill for the time they exist, and a confused agent can loop. Thirty minutes is a reasonable starting ceiling for a scoped task.
  4. Do not mount the Docker socket unless the task genuinely builds containers. A mounted socket is host root.
  5. Build the image on a schedule, not per job, so a fresh runner pulls one cached layer set rather than assembling its toolchain.

If you run agents on Kubernetes, the same rules apply to the pod: non-root, a read-only root filesystem outside the workspace, and network policies for egress per Solo.io.

Design cache keys so fast does not mean stale

Ephemeral runners lose the hidden warm state that makes a laptop fast: ~/.npm, ~/.cargo, Gradle and Maven caches, Docker layer stores, /tmp. On a long-lived runner that state is a speedup and also a hazard, because one job can write a poisoned cache entry that every later job inherits, producing a failure whose root cause ran hours earlier on a different pull request per this comparison of ephemeral and long-lived runners.

The fix is to move warm state out of the runner and into a cache you address explicitly. actions/cache works, with the caveat that the repository cache has a size ceiling shared across branches, and the maintainers suggest upload and download of artifacts or a registry cache for Docker layers when you need consistent performance rather than spiky hits per this ARC discussion.

A cache key is a correctness statement, not a label. Key on the inputs that change the output:

- uses: actions/cache@v4
  with:
    path: ~/.npm
    key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      npm-${{ runner.os }}-
  • hashFiles over the lockfile means a dependency change invalidates the cache automatically. Never put a branch name in the key: you get a cold cache on every new branch and stale hits when a branch is reused.
  • restore-keys gives you a partial hit from a recent key. That is a speed win and a small risk: a partial node_modules from a different lockfile can produce confusing errors, so keep restore keys coarse and let the exact key win whenever possible.
  • For build systems, key on the compiler toolchain and the source hash, and set the cache to read-only for pull requests from forks.
  • Cache regression is a real failure mode. If a build that normally takes 4 minutes takes 11, check the cache hit rate before you blame the code.

The honest tradeoff of ephemeral runners is cold start, and prebaked images plus explicit caches are the two levers that address it per RunsOn. Budget for it: a first run that pays the cache miss is normal, and if your median job spends most of its time restoring caches you have the wrong layer strategy, not the wrong runner model.

Scope secrets to the job, not the runner

Least privilege for secrets means constraining each credential to the narrowest scope, duration and resource set the workload needs, and revisiting that after deployment because permissions chosen at creation time are usually broader than the workload uses per the NHI glossary. The glossary's CI example is exactly this problem: a runner should get a token that can publish to one package registry, not a cloud-admin key.

Practical rules for a job that runs an agent:

Secret typeWhere it comes fromLifetimeWho can read it
Model API keyJob secret or OIDC exchangeThe jobThe agent process
Repository read tokenWorkflow's built-in tokenThe jobCheckout and agent
Package or registry write tokenSeparate secretThe publish step onlyThe gate job
Cloud credentialsOIDC, short sessionMinutesThe step that needs it
Deploy keysNot on the runnern/aNever

Four habits that do most of the work:

  1. Prefer OIDC over stored long-lived keys. Exchange the workflow's identity for a short session at the step that needs it, so nothing durable sits in the environment.
  2. Split read from write. The agent job gets read access to the repository. A separate gate job, which runs only after review and tests pass, holds the credential that can push to a shared branch or publish a package.
  3. Never put secrets in the agent's prompt or context. Anything the model can see can end up in a diff, a comment or a tool call. A reported figure is a useful warning here: users approved 93 percent of permission prompts in auto mode, which means dialogs are partly ceremony and not a reliable boundary per Penligent's summary.
  4. Assume the secret will be copied. OpenAI's agent safety guidance makes the same argument: design so that the impact of manipulation is constrained even when the manipulation succeeds per Penligent. A read-only token in a leaked transcript is an inconvenience. An admin token is an incident.

Ephemeral runners help here because secrets do not outlive the job and there is no cleanup step to forget, since the disk is gone per RunsOn. But that only holds if the credential was scoped in the first place. A runner that is destroyed with an admin token on disk for those minutes still leaked it.

Gate the result before it reaches a shared branch

Parallel agents create a second coordination problem that isolation does not solve: git worktrees prevent agents from editing the same working copy, but they do not prevent merge conflicts or incompatible changes when branches are combined per Superset's guide. Uncoordinated agents produce merge conflicts, duplicated implementations and semantic contradictions that pass compile and lint because each agent works from partial context per Augment Code.

A workable gate has four stages, and the agent is allowed through only in order:

  1. Scope check. The agent's brief names the files it may change and runs its own focus test. Superset's prompts explicitly end with "Do not commit, push, or merge," which keeps the human in the loop at the right point per Superset.
  2. Diff review. Inspect git status --short for unexpected files, then read the diff including changes outside the requested area per Superset. On a runner, this step can be a scripted check: fail if the diff touches files outside the allowlist, if it adds binary blobs, or if the line count exceeds a budget you set.
  3. Independent test run. The same job builds and tests the branch from a clean checkout. A second agent session with fresh context reading the diff is a cheap way to catch what the implementer missed; practitioners running many agents in parallel describe plan, implement and validate as separate sessions for exactly this reason per Cole Medin's walkthrough.
  4. Merge sequentially. Open a pull request, apply branch protection, and merge one at a time, re-running tests on the combined result per Superset. Parallel work is fine. Parallel merges are not.

If you want independent agents to converge before writing code, an alternative is to have them agree on a spec and route it through a mediator agent that decides whether proposals conflict, then let one implementer produce a single pull request as described in this orchestration talk. That trades per-agent isolation for fewer merge conflicts, and it fits when the tasks genuinely overlap rather than when they are independent.

For teams standardizing the agent half of this, the choice is mostly about where the model call goes. Quanta Inference (https://quantaintelligence.ai/inference) puts text, image, video, audio and embedding models behind one OpenAI-compatible key with prepaid dollar pricing, which keeps the runner's environment small: one base URL, one secret, no per-vendor SDK.

A worked example with numbers

Three agents, three scoped tasks, every job ephemeral. Call the costs illustrative and measure your own.

  • Without a cache, installing dependencies on a medium project takes 70 seconds and restoring a build cache takes 4 seconds. Three jobs per change, twice a day, five days a week, 50 weeks: 3 × 2 × 5 × 50 = 1,500 jobs. That is 1,500 × 66 seconds of difference, about 27.5 hours of runner time per year spent re-downloading dependencies.
  • A cache hit rate of 90 percent removes most of it. The remaining 10 percent is your cold start budget, roughly 2.75 hours.
  • A merged bad change that breaks the main branch for two hours costs far more, which is why the gate in the previous section is worth more than the cache.

The worked decision rule: spend engineering time on cache keys until the median job is dominated by the agent's own thinking, then spend the next block on the gate. If the agent's run is the short part of the job, you are paying for setup, not for intelligence.

The second worked rule is about where the agent runs. Start with one repository, one image digest and one job. Move a task to the runner when it needs dependencies, a database or a real credential. Keep reading and editing locally. When the pattern is stable and you want it on other repositories, the same image and gate logic can be packaged as a skill or an expert job rather than rebuilt per team; Quanta Skills (https://quantaintelligence.ai/skills) is the library behind every Quanta expert, installable into a coding agent or run as a managed job.

FAQ

FAQ

What exactly makes a runner ephemeral?

The machine is created for a single job and destroyed when the job ends. GitHub's runner supports an --ephemeral flag that accepts one job and then deregisters, and the Actions Runner Controller provisions runners that are ephemeral by design per this write-up. The property that matters is that no filesystem, cache or credential survives into the next job per RunsOn.

Do I still need git worktrees if I use ephemeral runners?

Not for isolation, because each job gets a fresh checkout. Worktrees remain useful on your laptop for reading and comparing several agent branches at once, and they keep a single working copy from being edited by two processes per the Claude Code docs. On CI, use one job per branch and let the runner do what worktrees were doing.

How do I keep builds fast without a warm runner?

Bake toolchains and the agent CLI into a pinned image, and move dependency and build caches into an explicit cache keyed on lockfile and toolchain hashes. actions/cache covers most cases, and artifact upload plus a registry cache handles Docker layers when cache hit rates are spiky per this discussion. Prebaked images and explicit caches are the two accepted answers to cold start per RunsOn.

Where should the agent's API credential live?

In the job's secret store or a short-lived OIDC session, scoped to the single model endpoint or registry the job needs, and never in the prompt or the repository. The CI example for least privilege is a token that can publish to one registry rather than a cloud-admin key per the NHI glossary. Give the publishing step its own credential so the agent job never holds write access to a shared branch.

What is the minimum gate before an agent's change reaches main?

A scope-limited diff review that fails on unexpected files, a full test run from a clean checkout, and a pull request that merges sequentially with tests re-run on the combined result per Superset. A fresh-context review pass, by a second agent or a person, catches the bugs the implementer missed per Cole Medin. Anything less and you are merging unreviewed machine output into a shared branch.

Sources

Sources

  1. Sandboxes for Coding Agents - Penligent
  2. Sandboxes Aren't Optional: 4 Ways to Isolate Coding Agents - YouTube
  3. Run multiple coding agents safely with git worktrees - Medium
  4. We ditched worktrees for Claude Code. Here's what we use instead
  5. What Is an Agent Sandbox? A Guide to Isolated Execution for AI Agents
  6. Ephemeral GitHub Actions runners
  7. Self-hosted runners reference
  8. Your CI runners are shared mutable state
  9. Complete non-ephemeral configuration examples? #711
  10. What Is Least Privilege For Secrets? Definition & Examples
  11. Run three AI coding tasks in parallel, then review each change - Superset
  12. How to Run a Multi-Agent Coding Workspace (2026) | Augment Code
  13. Parallel Claude Code + Git Worktrees: This Setup Will Change ...
  14. Multi Agent Orchestration Without Conflicts - YouTube
  15. Run parallel sessions with worktrees - Claude Code Docs
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles