Correction (2026-10-12): This post originally said our Sandboxes Edition 1 benchmark verified "100% Zero-Leak Compliance" with an independent before-and-after control-plane audit. That is not correct. The published
verify.pywas run by the agent inside each trial, only called each provider's listing API after the run, deleted any sandboxes it found and still passed, and also passed if the listing call failed. Leaks were therefore not verifiable, and Daytona Trial 4 shows one leftover sandbox that was silently cleaned up. The section below has been rewritten, and the E2B and Daytona cleanup calls corrected. Full list with evidence: Sandboxes Edition 1 errata (also in the research repository).
When autonomous AI coding agents execute tasks at scale, they provision hundreds—sometimes thousands—of disposable microVMs and container sandboxes each day.
When an agent succeeds, it usually calls the provider's cleanup API and tears down the container. But autonomous software development is inherently messy:
- LLM inference calls hit rate limits or time out mid-turn.
- The agent hits a recursion limit or gets stuck in a hallucination loop.
- Unhandled subprocess errors terminate the parent worker process abruptly.
- Network partitions sever WebSocket tunnels between the agent runner and the cloud sandbox provider.
When any of these failure modes occur, what happens to the remote microVM?
In unhardened agent architectures, the remote instance stays running indefinitely. It continues consuming CPU allocation, reserving public preview edge ports, and burning cloud credits. We call this phenomenon Agent Compute Sprawl—and it is one of the fastest ways to run up catastrophic surprise cloud bills.
In our empirical evaluation, The Glintbase Integration Index: Cloud Sandboxes for Autonomous AI Coding Agents, we ran 30 live trials across five leading sandbox providers and learned, partly the hard way, how easy it is for a leak to go unnoticed. Here is how to architect your agent workflows so that sandboxes are torn down, and so that a leak fails loudly when one does happen.
1. The Anatomy of an Agent Sandbox Leak
To prevent compute leaks, you must first understand where the breakdown happens in the agent execution lifecycle:
[Agent Starts] ──> [Provision MicroVM] ──> [Execute Task] ──> [Success] ──> [Explicit Cleanup] ✅ Clean
│
└──> [Timeout / Exception / Crash]
│
└──> [Parent Exits] ──> [MicroVM Remains Alive] ❌ Zombie Leak
When an exception occurs during the Execute Task phase, standard procedural code never reaches the subsequent cleanup call. The cloud provider's control plane continues treating the microVM as active because it received no explicit termination signal.
2. Pattern 1: Deterministic finally Blocks and Context Managers
The first line of defense is ensuring that cleanup is syntactically guaranteed in the host runtime, regardless of whether the execution succeeded, failed, or threw an unhandled exception.
Always wrap sandbox lifecycles in structured context managers or deterministic finally blocks:
- E2B: Ensure
sandbox.kill()is placed in thefinallyblock of your agent runner. - Modal: Ensure
sb.terminate()is executed whenever an exception is caught. - Daytona: Call
client.delete(sb, wait=True)unconditionally upon session completion. Withoutwait=Truethe delete is asynchronous, and the sandbox can still be listed right after the call returns (we saw exactly this in one benchmark trial). - boat.dev: Call
boat_sdk.stop_and_remove(api, sb_id, delete=True)in all exit paths. - Vercel Sandbox: Ensure the sandbox instance is stopped in the session teardown handler.
3. Pattern 2: Server-Side Time-to-Live (TTL) Hard Caps
Client-side cleanup code is necessary, but it is not sufficient. If the agent's host server loses power, suffers an out-of-memory kernel panic (OOM), or experiences a network partition, client-side finally blocks will never execute.
Therefore, you must enforce Server-Side TTL Limits directly on the provider's control plane:
- Always set a maximum session timeout upon creation: When instantiating a sandbox, pass an explicit maximum lifetime (e.g. 300 seconds for automated PR reviews, 900 seconds for complex test suites).
- Idle Timeout Enforcement: Configure the provider to automatically shut down containers if no command is executed within 60 to 120 seconds.
- Never rely on provider defaults: Some providers default to 24-hour timeouts or infinite persistence if no parameter is specified.
4. Pattern 3: Heartbeat Pings and Dead-Man Switches
For long-running autonomous agent sessions (e.g., automated migration bots running for 30+ minutes), a static TTL can prematurely kill active jobs.
The production-grade pattern is a Dead-Man Switch Heartbeat:
- When the agent runner provisions a sandbox, it registers a short 3-minute TTL.
- While the agent actively reasons and executes commands, an asynchronous background heartbeat extends the TTL by 2 minutes every 60 seconds.
- If the agent crashes, hangs indefinitely, or is terminated by the operator, the heartbeat immediately stops.
- The cloud control plane reaches the 3-minute TTL and automatically terminates the microVM.
5. Pattern 4: Control-Plane Garbage Collection Sweepers
In enterprise agent fleets running thousands of daily jobs across developer teams, orphaned instances will inevitably slip through edge cases.
Implement an independent, scheduled Garbage Collection (GC) Sweeper:
- Run a lightweight cron worker every 15 minutes.
- Query the cloud sandbox provider's control plane API for all currently active instances.
- Inspect the instance creation timestamp and metadata tags (e.g.,
agent_job_id,commit_sha). - If any instance has exceeded its expected workflow duration and lacks an active orchestrator heartbeat, issue an immediate forced termination.
What Our Benchmark Could (and Could Not) Verify About Leaks
(Rewritten 2026-10-12. The original section claimed a before-and-after control-plane audit by an independent auditor and "100% Zero-Leak Compliance" across all 30 trials. Neither claim is supported.)
During the Glintbase Integration Index (Edition 1) benchmark, every trial ended with a verify.py script that the agent ran inside its own trial. That verifier:
- Did not record instance counts before the trial. It only called each provider's sandbox-listing API after the run (no process tables, container runtimes or network listeners were checked).
- If any sandboxes were still listed, printed a warning, deleted them, and still reported the trial as passed.
- If the listing call itself failed, printed an info message and still passed.
So a passing run told us nothing about leaks, and "0 leaked sandboxes" is not verifiable for any of the five providers. The transcripts show this happening once: in Daytona Trial 4 the first verifier run printed [WARN] Detected 1 active sandboxes, deleted the sandbox, and passed. The cause was an asynchronous client.delete(sb); the agent changed it to wait=True and the re-run found nothing left over.
We have since fixed all 30 verifiers: they now fail with a non-zero exit when sandboxes remain (still cleaning up afterwards so billing stops), and a leak check that cannot run also fails.
The lesson for your own fleet is the same one this post argues for: a leak check that cleans up silently is a cleanup step, not a check. Make it fail loudly, combine it with TTL hard caps and deterministic exit handling, and wait for deletes to complete before you check.


