Hey @szabrown — great question
this is an important nuance when moving from CLI → Cloud.
You’re right: in local/CLI examples, you often manually call:
await backend.shutdown()
But in LangGraph Cloud, you don’t control process lifetime directly — the runtime does.
Key Concept: Treat Sandboxes as Per-Run Resources
In LangGraph Cloud:
-
Each invocation = isolated graph run
-
You should not rely on process-level teardown
-
You shouldn’t depend on global shutdown hooks
So instead of managing sandbox lifetime at the app level, you want to scope it to:
The graph run or thread lifecycle.
Recommended Patterns
Bind Sandbox Lifetime to Runtime Context (Best Practice)
Since your backend receives rt (runtime), create the sandbox inside the backend constructor and tie cleanup to run completion.
Instead of:
const sandbox = new Sandbox()
Use a pattern like:
backend: (rt) => {
const sandbox = new Sandbox()
rt.onShutdown(async () => {
await sandbox.shutdown()
})
return new CompositeBackend(...)
}
This ensures cleanup happens when the runtime tears down the execution context.
Make Sandboxes Ephemeral Per Invocation
If the sandbox is execution-scoped (e.g., code execution tool):
Example pattern:
async function runInSandbox(code: string) {
const sandbox = new Sandbox()
try {
return await sandbox.run(code)
} finally {
await sandbox.shutdown()
}
}
This is often cleaner in cloud deployments.
Avoid Global Singletons in Cloud
Don’t do:
let sandbox = new Sandbox()
Because:
Cloud ≠ long-running CLI process.
What NOT to Rely On
-
Process exit handlers
-
process.on("SIGTERM")
-
Manual shutdown scripts
LangGraph Cloud manages lifecycle for you.
Architectural Guideline
Ask:
Is this sandbox:
-
Per tool call?
-
Per thread?
-
Per deployment?
Most use cases → per tool call or per thread is safest.
Practical Rule of Thumb
If it’s:
Make it short-lived and explicitly closed in the same scope it was created.
If you share which sandbox backend you’re using (e.g., E2B, custom Docker, VM-based, etc.), I can give a more concrete recommendation.