fix(code): own sandbox lifetime from the client in non-interactive runs
Submission checklist
- This is a bug, not a usage question.
- I added a clear and descriptive title.
- I searched existing issues and didn't find this.
- I can reproduce this with the latest released version.
- I included a minimal reproducible example and steps to reproduce.
Area (Required)
- deepagents (SDK)
- dcode
- talon
- acp
- evals
- daytona
- modal
- quickjs
- runloop
- vercel
- langsmith-sandbox
- Other / not sure / general
Related Issues / PRs
https://github.com/langchain-ai/deepagents/pull/6368
Reproduction Steps / Example Code (Python)
E2B is self-hosted on two local servers (control plane and sandbox server). A single non-interactive run leaves its sandbox alive: after dcode exits, the Firecracker microVM is still running on the sandbox server.
Command to reproduce:
export DEEPAGENTS_CODE_DEBUG=1
export E2B_DOMAIN=e2b.internal
export E2B_API_KEY=xxx
export E2B_API_URL=http://192.168.100.2:3000
export E2B_SANDBOX_URL=http://192.168.100.2:3002
export MODEL_ID=""
export OPENAI_BASE_URL=""
export OPENAI_API_KEY=
dcode \
--model "openai:${MODEL_ID}" \
--model-params '{"use_responses_api":false}' \
--sandbox e2b \
--no-mcp \
--shell-allow-list "python,python3" \
-n "Create hello.py containing print('hello_world') in the sandbox. Execute python3 hello.py in the sandbox and report the actual output."Error Message and Stack Trace (if applicable)
Description
E2B is self-hosted on two local servers (control plane and sandbox server). A single non-interactive run leaves its sandbox alive: after dcode exits, the Firecracker microVM is still running on the sandbox server. It is destroyed a few minutes later, when the E2B sandbox timeout expires — not by dcode.
Expected: create_sandbox in integrations/sandbox_factory.py deletes the sandbox in its finally block when it created it (should_cleanup = sandbox_id is None), so the sandbox should go away when the run ends.
Actual: the delete is never issued. The sandbox is created in the langgraph server process (server_graph.py), which registers cleanup via atexit. The CLI stops that server with killpg(pgid, SIGTERM) (client/launch/server.py). CPython installs a handler for SIGINT but leaves SIGTERM at SIG_DFL, so the server is terminated by the kernel without running atexit, and provider.delete() never fires. Changing that signal to SIGINT in _terminate_server_process makes the sandbox disappear immediately, which confirms the path. hello-world test command:
Environment / System Info
No response
Source: langchain-ai/deepagents