feat(sandbox): add Daytona as a manual-flip failover for E2B#5860
feat(sandbox): add Daytona as a manual-flip failover for E2B#5860TheodoreSpeaks wants to merge 1 commit into
Conversation
E2B was a hard single point of failure: lib/execution/e2b.ts had no retry
and no fallback, so a failed Sandbox.create() killed Python function blocks,
JS-with-imports, shell, doc generation and the Pi cloud agent outright.
Extract a SandboxRunner boundary (lib/execution/remote-sandbox) with an E2B
runner and a Daytona runner, selected once per execution by the
sandbox-provider-daytona AppConfig flag. Everything above the provider
boundary — marker parsing, mount materialization, file export, corruption
handling — is unchanged.
Selection resolves before create() and never mid-execution, since user code
has side effects. Each sandbox kind fails closed when its snapshot id is unset.
Notes on the Daytona adapter:
- language binds at create(), not per call: Daytona applies it as a sandbox
label and silently runs JS through Python if passed to codeRun
- Python routes via CodeInterpreter for its {name,value,traceback} error shape,
which matches E2B's and keeps formatE2BError's line offsets correct
- timeouts convert ms to seconds
- the streaming path delivers env via the filesystem API, as
SessionExecuteRequest has no env field and secrets must not reach a command line
Drops the dead E2BExecutionResult.images field (populated, never consumed).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryHigh Risk Overview Call sites (function execute route, doc compile/extract/render/recalc, Pi cloud backends) are import renames only—behavior above the boundary (mounts, Adds Reviewed by Cursor Bugbot for commit e201d6c. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e201d6c. Configure here.
| rootUser: true, | ||
| onStdout: options.onStdout, | ||
| onStderr: options.onStderr, | ||
| }), |
There was a problem hiding this comment.
Pi sandbox PATH not merged
High Severity
withPiSandbox forwards runCommand envs without merging the standard Pi PATH that included /root/.local/bin. The removed e2b.ts wrapper always appended that path so the global pi CLI was found; without it, Create PR / Review git steps can fail with “command not found” depending on the image default PATH.
Reviewed by Cursor Bugbot for commit e201d6c. Configure here.
| // Daytona merges the two streams into `result`; splitting them back out is | ||
| // not possible, so stdout carries everything and callers that join the two | ||
| // (every caller today) are unaffected. | ||
| return { stdout: result.result ?? '', stderr: '', exitCode: result.exitCode ?? 0 } |
There was a problem hiding this comment.
Daytona stderr empty on failures
Medium Severity
Daytona runCommand puts merged command output in stdout and always leaves stderr empty. Shared shell handling and review-tool failures still treat stderr as the error text, so non-zero exits on Daytona often surface as generic exit-code messages instead of the real stderr content E2B returned separately.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit e201d6c. Configure here.
| (chunk: string) => options.onStderr?.(chunk) | ||
| ) | ||
| const finished = await this.sandbox.process.getSessionCommand(sessionId, commandId) | ||
| return { stdout: '', stderr: '', exitCode: finished.exitCode ?? 0 } |
There was a problem hiding this comment.
Streaming Pi run empty streams
Medium Severity
Daytona’s streaming runCommand path delivers output only via callbacks and returns empty stdout and stderr. When the Pi agent exits non-zero, cloud backends format errors with piRun.stderr || piRun.stdout, which are both blank on Daytona even though logs streamed correctly.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit e201d6c. Configure here.
Greptile SummaryThis PR adds Daytona as a manually selected remote-sandbox provider. The main changes are:
Confidence Score: 4/5Daytona-only failover remains blocked in function and document execution paths.
apps/sim/app/api/function/execute/route.ts and the provider availability logic used by document compilation. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Remote sandbox request] --> B{Legacy E2B gate allows path?}
B -->|No| C[Request rejected before provider selection]
B -->|Yes| D{Daytona feature flag enabled?}
D -->|No| E[E2B provider]
D -->|Yes| F[Daytona provider]
E --> G[Shared execution and export flow]
F --> G
Reviews (1): Last reviewed commit: "feat(sandbox): add Daytona as a manual-f..." | Re-trigger Greptile |
| exportedFileContent, | ||
| exportedFiles, | ||
| } = await executeShellInE2B({ | ||
| } = await executeShellInSandbox({ |
There was a problem hiding this comment.
Legacy Gate Blocks Daytona Execution
When Daytona is configured but E2B_ENABLED is false, the existing shell, Python, and JavaScript gates reject the request before this provider-neutral call can resolve Daytona. A Daytona-only failover configuration therefore cannot run function blocks; entry into the remote-sandbox path must depend on the selected provider's availability.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| @@ -259,7 +263,7 @@ async function compileDocViaE2BPython( | |||
| // unaffected. Runs only after the user's script succeeds. | |||
There was a problem hiding this comment.
E2B Gate Hides Daytona Documents
Document compilation still enters this path through isE2BDocEnabled, which requires the E2B API key and E2B document template. With Daytona selected and DAYTONA_DOC_SNAPSHOT_ID configured, removing the unavailable E2B configuration marks the compiler unavailable before this call runs, so document generation fails during the intended failover.


Summary
lib/execution/e2b.tshad no retry and no fallback, so one failedSandbox.create()killed Python function blocks, JS-with-imports, shell, doc generation, and the Pi cloud agentSandboxRunnerboundary (lib/execution/remote-sandbox) with an E2B runner and a Daytona runner, selected by thesandbox-provider-daytonaAppConfig flag — flip it during an E2B outage, no redeployscripts/verify-sandbox-parity.ts— 8 live executions against whichever provider the flag selects, as the pre-flip confidence checkE2BExecutionResult.imagesfield (populated, never consumed)Companion: simstudioai/mothership#369 (builds the shell + doc images on Daytona)
Notes
Selection resolves once before
create()and never mid-execution, since user code has side effects. Every sandbox kind fails closed when its snapshot id is unset.Three Daytona quirks the adapter absorbs:
languagebinds atcreate(), not per call — Daytona applies it as a sandbox label and silently runs JS through Python if passed tocodeRunCodeInterpreterfor its{name,value,traceback}error shape, which matches E2B's and keepsformatE2BError's line offsets correctSessionExecuteRequesthas noenvfield and secrets must not reach a command lineKnown caveat: Daytona cold-starts a sandbox in ~45s when it lands on a runner without the image cached (warm is ~800ms vs E2B's ~200ms). It costs wall-clock, not correctness —
timeoutMsgoes torunCode, notcreate, and plan sync budgets are 300s/3000s.Type of Change
Testing
verify-sandbox-parity.tsagainst both providers with live sandboxes: E2B 8/8, Daytona 8/8 (marker round-trip, data-science imports, structured errors, outbound network, JS-under-node, shell envs, file mount + text export, xlsx compile + base64 binary export)bun run lintandbun run check:api-validation:strictpass;tsc --noEmitcleancloud-review-tools.test.tsspawning Python that needs a realrgbinary, absent locallyChecklist