Skip to content

Terminate the VM a gateway deploy created when the deploy fails or is cancelled - #141

Merged
earakely-scale merged 3 commits into
mainfrom
edgararakelyan/gateway-deploy-cleanup
Oct 9, 2026
Merged

earakely-scale merged 3 commits into
mainfrom
edgararakelyan/gateway-deploy-cleanup

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

A gateway deploy on a VM-mode provider creates its VM first, then loads images, starts the compose stack and waits for the gateway and its card. If anything after create_vm failed, or the deploy was cancelled (for example Ctrl-C during "Starting MCP services…"), the VM kept running:

  • no record named it yet, so teardown couldn't find it;
  • the provider's close() ends only container-mode sandboxes;
  • env._sandbox is set only after deploy() returns.

On the local provider that "VM" is a compose stack on your machine, so it held ports and memory until removed by hand. A chained provider (beta_scale,modal_vm) also left one VM behind for each member that failed before another succeeded.

Before / after, measured on local with real Docker:

Before (main) After
Cancelled while the compose stack comes up 5 containers left, Created nothing left
Gateway never becomes ready (stack up) the whole stack left running nothing left
Chained local,local, first member fails its stack left running beside the winner's only the winner's stack
A successful deploy, then teardown removed removed, with no extra terminate
A caller's existing_sandbox left alone left alone

How

  • _deploy_gateway: when the VM path raises, including on cancellation, the attempt terminates the VM it created (_terminate_new_sandbox). It never terminates the caller's existing_sandbox, one an earlier deploy made, or a container sandbox; close() already ends those. Because this happens per attempt, each failed chained member cleans up after itself.
  • _launch: a cancellation during the tools probe, after a successful attempt, does the same. The probe itself never raises.
  • _run: closes on BaseException, so a cancelled deploy releases its containers and state like a failed one.
  • MCPServerEnv / WebsiteEnv / MultiEnv close(): catch Exception rather than BaseException around terminating the sandbox. A cancellation now propagates instead of being swallowed, and the sandbox is kept for the next close().

Verified

A live driver ran on local with real Docker, using its own registry, stores and sandbox root. The faults are real: a cancel while the stack comes up, and a gateway that never becomes ready (injected at _wait_for_gateway, so it fails at once rather than after 300 s). The five scenarios in the table pass on this branch. The first two leak as shown on unmodified main.

Unit tests pass. New ones cover:

  • a failed and a cancelled VM attempt;
  • the caller's VM left alone;
  • each failed chained member's VM terminated while the winner keeps its own;
  • a cancellation during the probe;
  • each env's cancelled close() keeping its sandbox.

Not covered here

  • A second deploy() on the same env object that fails can still tear down the first deployment. Only direct Python callers do that, since task steps always load a fresh env.
  • Chained members sharing one external state instance.
  • A failed local deploy still leaves its empty work dir under ~/.agent-env-sandboxes. The stack is gone.

🤖 Generated with Claude Code

RetriggerConfidence Score: 5/5

The PR appears safe to merge; the earlier suggestion to retry failed VM cleanup remains non-blocking.

Fix All in CursorFindings

  1. P2 Failed cleanup cannot be retried ▶
Fix with agent prompt
### Issue 1
src/agent_env/providers/env_providers/env_gateway_provider.py:623-629
If `terminate()` fails, `_discard_vm` logs the error after removing the only saved VM handle. For example, `LocalSandbox.terminate()` raises when `docker compose down` fails. Provider `close()` does not retry VM termination, so the caller must remove the leftover stack or VM by hand.

As a non-blocking improvement, keep failed terminations in a pending-cleanup list that `close()` retries, and add a test for this path.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Failed or cancelled gateway deployments now terminate the VM they created. Caller-owned sandboxes are left alone.

  • Failed gateway attempts clean up only the sandbox they created.
  • Cancelled teardown keeps the sandbox available for another try.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Start gateway attempt] --> B[Create VM]
    B --> C[Load images and start services]
    C --> D{Attempt succeeds?}
    D -->|No or cancelled| E[Terminate newly created VM]
    E --> F[Close containers and state]
    F --> G[Try next provider or raise]
    D -->|Yes| H[Probe tools]
    H --> I{Probe cancelled?}
    I -->|Yes| E
    I -->|No| J[Return deployment]
Loading

Reviews (3) · Last reviewed commit: "Call it _terminate_new_sandbox: what kin..." · Reviewed by Greptile

… cancelled

A VM-path gateway deploy that failed or was cancelled after create_vm left the VM running, or on
the local provider its compose stack: no record named it yet, and close() ends only container
sandboxes. Each attempt now terminates the VM it created when it ends that way, as does a
cancellation during the tools probe, but never the caller's existing sandbox or one an earlier
deploy made. A chained provider's failed members no longer each leave theirs. A cancelled deploy
is closed like a failed one, and an env's close() lets a cancellation through, keeping its
sandbox for the next close().

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner October 9, 2026 15:39
Comment on lines +623 to +629
created, self._sandbox = self._sandbox, before
if created is None or created is before or created is kept or created in self._container_sandboxes:
return
try:
await created.terminate()
except Exception as e:
logger.warning(f"Failed to terminate {type(created).__name__} {created.sandbox_id} after a failed deploy: {e}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Failed cleanup cannot be retried

If terminate() fails, _discard_vm logs the error after removing the only saved VM handle. For example, LocalSandbox.terminate() raises when docker compose down fails. Provider close() does not retry VM termination, so the caller must remove the leftover stack or VM by hand.

As a non-blocking improvement, keep failed terminations in a pending-cleanup list that close() retries, and add a test for this path.

Knowledge Base Used: Provider integrations and sandbox deployment

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/providers/env_providers/env_gateway_provider.py
Line: 623-629

Comment:
**Failed cleanup cannot be retried**

If `terminate()` fails, `_discard_vm` logs the error after removing the only saved VM handle. For example, `LocalSandbox.terminate()` raises when `docker compose down` fails. Provider `close()` does not retry VM termination, so the caller must remove the leftover stack or VM by hand.

As a non-blocking improvement, keep failed terminations in a pending-cleanup list that `close()` retries, and add a test for this path.

**Knowledge Base Used:** [Provider integrations and sandbox deployment](https://app.greptile.com/scale-ai/-/custom-context/knowledge-base/scaleapi/agentenv-framework/-/docs/provider-integrations.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not taken: close() runs moments after the failed terminate, so a retry there would most likely hit the same error (Docker unreachable, compose down failing). The warning names the sandbox, so it can be removed by hand.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That’s fair. A retry from the immediately following close() would likely encounter the same provider/Docker failure, while adding durable pending-cleanup state and retry policy would expand this change beyond the deploy-cancellation fix. The warning includes the sandbox type and ID, so operators still have an actionable recovery path. I agree this is a non-blocking operational improvement and does not need to be addressed in this PR.

earakely-scale and others added 2 commits October 9, 2026 08:49
…er and the caller's sandboxes by name

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rovider's concern

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit 09abbf4 into main Oct 9, 2026
13 of 14 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/gateway-deploy-cleanup branch October 9, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant