ai-sandbox-daytona: resume() returns a handle to a stopped sandbox — thread reuse does not work after the 15-minute auto-stop
@tombeckenham is already working on this.
Since Aug 21, 2026.
- Dominant language
- TypeScript
- Stars
- 3.1k
- Forks
- 331
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 160
Description
Versions: @tanstack/ai-sandbox-daytona 0.2.1, @tanstack/ai-sandbox 0.3.0, @daytona/sdk v0.191.0.
Summary
Daytona stops an idle sandbox after 15 minutes by default. The adapter does not set autoStopInterval. A stopped sandbox keeps its filesystem, and one SDK call (sandbox.start()) starts it again. resume() does not make that call. Thus lifecycle: { reuse: 'thread' } operates only when runs occur less than 15 minutes apart.
Mechanism
- Run 1 creates a sandbox, completes bootstrap, and ends.
destroyOnComplete: falsekeeps the sandbox. - The sandbox is idle for 15 minutes. Daytona stops it. The filesystem stays.
- Run 2 starts in the same thread.
ensure()callsprovider.resume(packages/ai-sandbox/src/sandbox.ts). DaytonaProvider.resume(packages/ai-sandbox-daytona/src/provider.ts) only callsdaytona.get(id)and wraps the result.daytona.getis a metadata request. It is successful for stopped and archived sandboxes. It does not start the sandbox (sdk-typescript/src/Daytona.tsin daytona/clients, tag v0.191.0).ensure()accepts each non-null handle as a started sandbox. The fallback to snapshot restore or to a new sandbox occurs only whenresumereturnsnull.- The first
executeCommandgoes to the sandbox toolbox. The toolbox returns HTTP 400 with a message that ends withIs the Sandbox started?. The run ends with this error.
lifecycle.keepAlive: '30m' gives no protection. Per docs/sandbox/lifecycle.md, no code reads keepAlive. The adapter sends no idle limit to Daytona.
Expected
docs/sandbox/providers.md says that Daytona has resume-by-id. A resumed handle must accept commands.
Impact
Each reuse: 'thread' deployment on Daytona gets this error when two runs are more than 15 minutes apart. For human chat threads, this is the usual case, not a rare case. Durable-run takeover with resume-by-id has the same defect. The user sees an HTTP 400 error in the run, not a cold start.
Fix direction
- In
resume(), readsandbox.state. - If the state is
stoppedorarchived, callawait sandbox.start()before you wrap the handle. - You can also add
autoStopIntervaltoDaytonaSandboxConfig(minutes;0= off).
With the state check, Daytona becomes a durable resume-by-id provider. The filesystem already stays through a stop. This also gives #1029 a Daytona path with no new code.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.