Skip to content

Commit fcb95c3

Browse files
cli: replace sandbox.kill() with setTimeout(1s) in create command to prevent snapshot deletions (#1256)
## Summary In the CLI's `sandbox create` command, the `connectSandbox` function's `finally` block previously called `sandbox.kill()` when the terminal session ended. This replaces it with `sandbox.setTimeout(1_000)` so the sandbox expires implicitly after 1 second rather than being explicitly killed. The motivation is that an explicit `kill()` can trigger deletion of historic sandbox snapshots, whereas letting the sandbox time out avoids that side effect. ### Updates since last revision Addressed review feedback about a race condition: `clearInterval` stops future keep-alive ticks but cannot cancel one already in-flight. The keep-alive callback now stores its promise in a `pendingKeepAlive` variable, and the `finally` block awaits it (with `.catch(() => {})`) before setting the 1s shutdown timeout. This ensures an in-flight `setTimeout(30_000)` cannot silently override the shutdown timeout. ## Review & Testing Checklist for Human - [ ] **Verify that `sandbox.setTimeout(1_000)` does not trigger snapshot deletion** — this is the core assumption behind the change. Confirm that the implicit expiry path in the backend behaves differently from the explicit `kill()` path with respect to snapshot preservation. - [ ] **Test `e2b sandbox create` end-to-end**: connect a terminal, exit, and confirm the sandbox is cleaned up within a few seconds and no snapshots are lost. - [ ] **Review the race condition fix**: confirm that `await pendingKeepAlive.catch(() => {})` correctly serializes against the last in-flight keep-alive before the 1s timeout is applied. Note that the interval callback is no longer `async` — it just assigns the promise. - [ ] **Edge case: what happens if `setTimeout` fails?** The sandbox would remain alive with its previous 30s keep-alive timeout. Decide if that's acceptable or if a fallback is needed. ### Notes - The 1-second timeout value was chosen per the request. Adjust if a different grace period is preferred. - The keep-alive interval (`clearInterval`) is still stopped before awaiting the pending promise, so no new ticks will fire. Link to Devin session: https://app.devin.ai/sessions/68081ba06fa54be9b8127ba1d68481ae --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ben@e2b.dev <ben@e2b.dev>
1 parent b5f2631 commit fcb95c3

File tree

2 files changed

+11
-4
lines changed

2 files changed

+11
-4
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@e2b/cli': patch
3+
---
4+
5+
Replace sandbox.kill() with sandbox.setTimeout(1s) in the CLI create command to prevent accidental historic sandbox snapshot deletions

packages/cli/src/commands/sandbox/create.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,10 @@ export async function connectSandbox({
9898
sandbox: e2b.Sandbox
9999
template: Pick<e2b.components['schemas']['Template'], 'templateID'>
100100
}) {
101-
// keep-alive loop
102-
const intervalId = setInterval(async () => {
103-
await sandbox.setTimeout(30_000)
101+
// keep-alive loop — track the in-flight promise so we can await it on shutdown
102+
let pendingKeepAlive: Promise<void> = Promise.resolve()
103+
const intervalId = setInterval(() => {
104+
pendingKeepAlive = sandbox.setTimeout(30_000)
104105
}, 5_000)
105106

106107
console.log(
@@ -112,7 +113,8 @@ export async function connectSandbox({
112113
await spawnConnectedTerminal(sandbox)
113114
} finally {
114115
clearInterval(intervalId)
115-
await sandbox.kill()
116+
await pendingKeepAlive.catch(() => {})
117+
await sandbox.setTimeout(1_000)
116118
console.log(
117119
`Closing terminal connection to template ${asFormattedSandboxTemplate(
118120
template

0 commit comments

Comments
 (0)