E2B provider
July 10, 2026 · View on GitHub
Provider status: shipped. Tasks 1–3 are all merged into
main; E2B is a fully supported AgentBox cloud backend (--provider e2b) alongside docker / daytona / hetzner / vercel. Public docs live at/docs/e2b; the provider page (apps/web/content/docs/e2b.mdx),CLAUDE.md,docs/cloud-providers.md§3c,docs/cloud-create-flow.md, and the README are kept in sync with each change.The standout differentiator vs. Daytona / Hetzner / Vercel: E2B is the only AgentBox cloud whose
preparebuilds the base image directly from a Dockerfile via the SDK'sTemplate.build(). The others all bake a one-time snapshot.
Status tracker for adding E2B (--provider e2b) as a fifth AgentBox backend,
alongside docker / daytona / hetzner / vercel.
E2B (https://e2b.dev) runs Firecracker microVMs with a TypeScript/Python SDK
(e2b). Shape-wise it is closest to Vercel (microVM per box, SDK comms, no
SSH, public preview hostnames, pause/resume persistence) and Daytona (snapshot
tiers). The Vercel provider (packages/sandbox-vercel/) is the structural
template for this work; read it first.
How E2B maps onto the CloudBackend abstraction
| AgentBox concept | E2B primitive |
|---|---|
| provision a box | Sandbox.create(template, { timeout, metadata, envs, ... }) |
| resolve existing box | Sandbox.connect(sandboxId) (auto-resumes if paused) |
| exec | sandbox.commands.run(cmd, { cwd, envs, user, background }) |
| upload / download / ls | sandbox.files.write / read / list |
| preview URL | sandbox.getHost(port) → {port}-{sandboxId}.{domain} (HTTPS) |
| pause / resume | sandbox.betaPause() / Sandbox.resume(id) (persistence) |
| list (for prune) | Sandbox.list() (paginator) |
| destroy | sandbox.kill() |
| session timeout | Sandbox.create({ timeout }) (seconds) + setTimeout |
| base image (prepare) | e2b template build from a Dockerfile — E2B CAN build images from a Dockerfile (key difference from Vercel/Hetzner, which can't) |
| egress policy | allow_internet_access / network create opts |
| credentials | E2B_API_KEY (in .env.local and ~/.agentbox/secrets.env); optionally team id |
Key open questions — answered in Task 1 (2026-06-02)
- Preview URL auth — PUBLIC by default.
sandbox.getHost(port)returns{port}-{sandboxId}.e2b.app, served over HTTPS with no token. Smoke-verified with apython3 -m http.server 8080in-box → host fetch returned HTTP 200 without any header. Optional gate:Sandbox.create({ network: { allowPublicTraffic: false } })requires ane2b-traffic-access-tokenheader (sandbox.trafficAccessToken). Task 1 leaves it public to match Vercel; revisit in Task 2/3 if the security model warrants gating. - Checkpoint primitive — Deferred to Task 2. E2B's
Sandbox.pause/Sandbox.connect(auto-resume) is a single-resume cold-store, not a reusable immutable image. The reusable primitive isTemplate.build()from a Dockerfile — that lands in Task 2 alongsideagentbox prepare --provider e2b. Task 1 ships a checkpoint stub that throws "not yet implemented for e2b (Task 2)". - Privileged ports / port cap —
getHost(port)accepts any port; no documented cap. Task 1 setswebProxyPort: 8080to mirror Vercel and keep the in-boxAGENTBOX_WEB_PROXY_PORTflag uniform across cloud backends. Re-test :80 in Task 2. - Nested containers — Ships
launchDockerd: false. NOTE (corrected 2026-06-23): the "same family as Vercel → no DinD" inference here was WRONG. E2B's microVM actually supports nested containers (full root +cap_sys_admin+ working namespaces); docker runs once installed. Thefalsedefault is a deliberate AgentBox choice (docker not baked in), not a platform constraint. See the changelog entry + the "DinD on E2B" follow-up. - Default resources — E2B
basetemplate ships node 20, sudo, git, tar on Debian 12. vCPU/RAM/disk are template-level (Template.build({ cpuCount, memoryMB })), NOT per-create. Task 1'sdefaultResources: { cpu: 2, memory: 4, disk: 8 }are advisory metadata for BoxRecord stats until Task 2'spreparebakes a sized custom template.
Additional empirical findings (smoke-tested 2026-06-02):
- Default user is
user(uid 1001), notvscode. Task 1'sprovision()runs a one-shot in-box fixup script that creates avscodeuser (auto-assigned uid —1000is taken by E2B'scodegroup onbase), grants passwordless sudo, and chowns/workspace,/run/agentbox,/var/log/ agentboxso the rest of the cloud scaffold's hardcodedvscodereferences work. The vanillabasetemplate otherwise has no agentbox-ctl, no /workspace, no vscode user. agentbox-ctlruns from a single ~835 KBpackages/ctl/dist/bin.cjsbundle. Task 1 uploads it at create-time viasb.files.write(~1s), installs to/usr/local/bin/agentbox-ctlwithsudo cp+chmod. No template bake needed for Task 1.Sandbox.getInfois the non-resuming static existence check.Sandbox.connectauto-resumes a paused sandbox —state()/get()MUST usegetInfo(not connect) so existence checks don't wake (and bill) a paused box. Only ops that need a live handle (exec, files, previewUrl, pause, destroy) call connect.Sandbox.pauseis the canonical pause API —betaPauseis deprecated.sb.commands.runthrowsCommandExitErroron non-zero exit. The CloudBackend contract returns{exitCode, stdout, stderr}, so the e2b backend catches the error and converts it back to a result.- Carry needs the same vercel root carve-out. Default exec runs as vscode,
but vscode (uid 1000) cannot
chownto other uids — the carry chain'schown -Rerrors out partway, the parent-chain loop never reaches its terminator.packages/sandbox-cloud/src/carry.tsnow forcesuser: 'root'for both vercel AND e2b.
Task breakdown (each task = one PR, merged before the next starts)
Task 1 — Package scaffold + CloudBackend core · status: DONE 2026-06-02
Goal: agentbox create --provider e2b produces a ready box end-to-end,
reusing the createCloudProvider scaffold.
-
packages/sandbox-e2b/package (package.json, tsup, tsconfig). Addse2b@^2.27.1. -
env-loader.ts+credentials.ts— loads/ensuresE2B_API_KEYfrom~/.agentbox/secrets.env;agentbox e2b login [--status]. -
sdk.ts— re-exportsSandbox, resolves the API key, gates with actionable error. -
backend.ts— everyCloudBackendmethod over the E2B SDK (provision, get, list, start/stop/pause/resume/destroy, state, exec, uploadFile/downloadFile/listFiles, previewUrl, signedPreviewUrl).get/stateuseSandbox.getInfo(non-resuming) per the orchestrator's review; only exec/files/pause/destroy useSandbox.connect. -
index.ts—createCloudProvider(e2bBackend, { defaultResources, launchDockerd: false })+ a checkpoint stub that throws "not yet implemented for e2b (Task 2)". Exportse2bProvider,e2bBackend,ensureE2bCredentials. -
cli.ts—agentbox e2b login [--status]. -
runtime-assets.ts— single-file resolver forpackages/ctl/dist/bin.cjs, uploaded at create-time so the cloud scaffold'slaunchCloudCtlDaemonfinds/usr/local/bin/agentbox-ctl. (Task 2 grows this to a full prepared-template bake.) -
test/env-loader.test.ts— parser + lookup precedence unit tests. - Wired into
apps/cli:provider/registry.ts,provider/cloud-backend.ts,index.ts,help.ts,commands/{checkpoint,prune,install,prepare,dashboard,fork}.ts,lib/doctor-checks.ts,packages/relay/src/host-actions.ts,packages/sandbox-cloud/src/carry.ts(root carve-out for the chown walk),packages/config/src/types.ts(ProviderKind+box.providerenum),test/help.test.ts. - Smoke:
agentbox create --provider e2b -y -n e2bsmoke -w /tmp/e2bsmoke-reporeachesbox cloud:<id> readyin ~15s. Backend-level smoke (/tmp/e2b-smoke-checks.mjs): - state → running - exec as vscode + as root → exit 0 - file round-trip (uploadFile / downloadFile) → byte-exact - listFiles →[{name,isDir}]- previewUrl →https://8080-<sbx>.e2b.app; HTTP 200 with no token - pause → 'paused'; start (connect auto-resume) → 'running'; in-box file survives the cycle - list → both live sandboxes seen - destroy → SandboxNotFoundError on subsequent getInfo (cleanly gone)
Task 2 — prepare (template build) + attach + checkpoints · status: DONE 2026-06-03
Goal: provider parity with Vercel — prepare bakes a custom template, interactive
attach works, checkpoints capture/restore. ALL smoke steps verified end-to-end.
-
prepare.ts+prepared-state.ts—agentbox prepare --provider e2bbakes the base template via the SDK'sTemplate.build()(driven from TypeScript, not an on-disk Dockerfile). Reuses the staged docker runtime assets throughruntime-assets.ts(mirror of vercel's resolver). Records template id in~/.agentbox/e2b-prepared.json. Base-snapshot gate (ensureE2bBaseTemplate) lives insidebackend.provision. -
build-attach.ts+attach-helper.ts— SDK-streaming PTY bridge (no SSH).buildE2bAttachreturns['node', <helper>, '--sandbox-id', id, '--user', vscode]withE2B_API_KEY+AGENTBOX_E2B_INNER_CMDin env. The helperSandbox.connects, openspty.create({ cols, rows, onData, ... }), sendInputs the renderInnerCommand string (tmux ensure + attach), and bridges stdin / stdout / SIGWINCH. -
checkpointcapability —Sandbox.createSnapshot(sandboxId, { name })is the reusable-snapshot primitive (was the missing piece in Task 1; same shape as Vercel — store snapshotId in the manifest, restore boots viaSandbox.create({ template: <id> })).snapshotExistsusesTemplate.exists(name). -
previewUrlfix — dropsSandbox.connect(which would auto-resume a paused box), constructs the URL locally as{port}-{sandboxId}.{E2B_DOMAIN ?? 'e2b.app'}. Verified:agentbox listshows the URL of a paused box without waking it. - Per-provider config keys (
box.imageE2b,box.defaultCheckpointE2b,box.sizeE2b) added so the prepare command pins the template id under the right key (avoids cross-provider collision). - CLI integration:
apps/cli/scripts/stage-runtime.mjsstagesruntime/e2b/{scripts/build-template.sh, attach-helper.cjs, ctl.cjs, agentbox-*-shim, custom-system-CLAUDE.md, ...};apps/cli/src/commands/ prepare.tsrenders the e2b status block;apps/cli/src/lib/doctor-checks.tsadds the base-template probe. - Unit tests:
test/prepared-state.test.ts(round-trip + gate),test/build-attach.test.ts(argv shape + env wiring). - Smoke end-to-end (2026-06-03):
-
agentbox prepare --provider e2b -y→ builtagentbox-base:latest(template ido05kawibx9vcmxvgjnk4); 2m18s build, pinned tobox.imageE2bin project config + recorded in~/.agentbox/e2b-prepared.json. -agentbox create --provider e2b -y -n e2bt2a -w /tmp/e2bsmoke-repo→ ready in ~10s (Task 1 was ~15s with the create-time fixup hop). -agentbox shell e2bt2a(viascript -q -c …) → realvscode@e2b:/workspace$prompt; the host PTY bridges in/out cleanly. (drive harness shows a display quirk where tmux's framing buffer doesn't render into the headless terminal — separate issue tracked below; the underlying PTY bridge works.) -agentbox stop e2bt2a→ statepaused;agentbox list --globalstill shows the URL (no resume);agentbox start e2bt2a→running; round-trip survives. -agentbox checkpoint create e2bt2a→Sandbox.createSnapshotmintedmarco6/agentbox-e2bt2a-…:default; manifest written under~/.agentbox/cloud-checkpoints/e2b/…/manifest.json;agentbox checkpoint lsfinds it. -agentbox create --provider e2b -y -n e2bt2b -w /tmp/e2bsmoke-repo --snapshot e2bt2a-150413→ booted from the snapshot id. -agentbox checkpoint rm e2bt2a-150413→ snapshot deleted on E2B, manifest cleared. -agentbox destroy -y e2bt2a e2bt2b→ both sandboxes destroyed.
Empirical findings (Task 2, 2026-06-03)
- Checkpoints capture RAM + disk, not just disk. E2B pause/resume is a
full memory snapshot —
Sandbox.pausepersists RAM + filesystem andSandbox.connectresumes with running processes intact.createSnapshotrides the same mechanism (it pauses the source while capturing), so an e2b checkpoint includes the memory state. This differs from the docker provider, whose checkpoint isdocker commit(disk only). Restoring an e2b box can therefore bring back in-flight processes; a docker box cannot. - The platform DOES support DinD (empirically verified 2026-06-23 — see
changelog; subsequently shipped on by default). Unlike Vercel, the E2B
microVM grants full root with the complete capability set
(
CapEff: 000001ffffffffff, incl.cap_sys_admin), workingunshare --user/--mount, and cgroup v2 — sodocker.io+dockerd(overlay2) runs containers inside an agentbox e2b box. This is now baked into the base template withlaunchDockerd: true(see the shipped changelog entry); the original "same as Vercel → no DinD" inference was wrong. Template.buildrequires RELATIVE source paths. The SDK'stemplate.copy(src, dest)rejects absolute paths (Invalid source path "/foo": absolute paths are not allowed). We stage every resolved asset into a tempfileContextPathdir under its logical name, then pass the asset name as a relativesrc— same idea as Docker'sCOPYsemantics.Sandbox.create({ template })auto-appends:default. A 404 fires withtag 'default' does not exist for template 'xxx'when thetemplateIdwe store omits the tag. We persist${templateId}:${tag}(tag =info.tags[0] ?? 'latest') so the create path always lands on a built tag.e2bSDK must be external to apps/cli's bundle. TheTemplateBuilderconstructor callsdynamicRequire('node:url')to resolve the caller directory; esbuild's ESM__requireshim throws "Dynamic require of 'node:url' is not supported" when this is bundled. Added'e2b'to apps/cli'sexternallist (same pattern as@daytonaio/sdkand@vercel/sandbox) and added it as a real dep.pty.createaccepts a maxtimeoutMsof 1 hour on the Hobby tier (400: Timeout cannot be greater than 1 hours). The attach helper now caps at 55 minutes; longer sessions will need a keepalive ping that extends the timeout viaSandbox.setTimeoutmid-session (future work).- Drive harness display quirk —
node-ptyvia the drive harness shows an empty screen when the spawned child is the agentbox shell attach, but a plainscript -q -c …capture shows the same attach producing a real prompt. Likely a headless-terminal-vs-tmux passthrough mismatch in the drive harness rather than the attach helper itself. Filed for later.
Task 3 — prune + docs + polish · status: DONE 2026-06-03
Goal: close the provider out — wire prune, finish doctor, propagate E2B everywhere the docs still said "four backends", and finalize the backlog as shipped.
-
agentbox prune --provider e2b—CLOUD_PRUNE_PROVIDERSalready includede2bfrom Task 1; live-verified the orphan-sweep path (sandbox spawned via the SDK with theagentboxmarker tag, thenprune --provider e2b --dry-runand-ycleanly removed it). Templates are sandbox-only — E2B's SDK exposes noTemplate.list(), so prune covers sandboxes only (documented in the provider page). -
agentbox doctor --provider e2b— credentials + prepared-template checks were added in Tasks 1–2; Task 3 fixed the stale--providerhelp/error strings inapps/cli/src/commands/doctor.tsthat still listed onlydocker | daytona | hetzner | vercel. - Internal docs synced:
CLAUDE.md(five backends, ctl shipping list, checkpoint primitive list, ops paragraph, doc-map),docs/cloud-providers.md(intro table + §3c "The E2B shape" + §4 auth + §8 file-map + footer),docs/cloud-create-flow.md(cross-provider callout),README.md(provider table + login + prepare + docs link). - Public site (Fumadocs) synced: new
apps/web/content/docs/e2b.mdx(mirrorsvercel.mdx, leads with the Dockerfile/Template.build()differentiator),meta.jsonproviders list updated,cli.mdxprovider section updated. - Backlog finalized — "provider status: shipped" header above; deferred follow-ups summarized below.
Task 4 — credential seed unification + bypass-permissions accept · status: DONE 2026-06-03
Goal: fix two showstoppers that landed inside the post-ship agentbox e2b claude
flow — E2B's ~/.agentbox-creds/<agent>/ ended up empty (Claude reported "Not
logged in") and every fresh box hit Claude Code's one-time "Bypass Permissions
mode … Yes, I accept" gate.
- Bug 1 — credentials seeding.
cloud-provider.ts'sseedAgentVolumesIfFreshwas gated onagentVolumes.agents.length > 0, andensureAgentVolumesForCloudreturnedagents: []for any backend without anensureVolumeprimitive. Vercel and Hetzner each duplicated the push in their own backends (pushVercelAgentCredentials,pushHetznerAgentCredentials); E2B had no equivalent. Unified the path:ensureAgentVolumesForCloudnow returns the full agent list (['claude', 'codex', 'opencode']) for non-volume backends too, andseedCredentialsOnebranches onbackend.ensureVolume: volume backends keep the FUSE-friendly cp-via-staging +.agentbox-seeded-atmarker; ephemeral backends dosudo -u vscode tar -xzf … --no-same-permissions --no-same-owner -mstraight into the box-baked~/.agentbox-creds/<agent>/dirs every create (tokens are renewable, the box FS is ephemeral). Dropped the Vercel + Hetzner custom pushers. - Bug 2 — bypass-permissions accept gate. Added
"skipDangerousModePermissionPrompt": truetopackages/sandbox-docker/scripts/claude-managed-settings.json. Disassembly of~/.local/share/claude/versions/2.1.150showed the gate is suppressed when any ofuserSettings | localSettings | flagSettings | policySettingssets that key —policySettingsis/etc/claude-code/managed-settings.json, which the docker and cloud installers already bake. Docker boxes happened to skip the gate because the host's~/.claude/settings.json(mounted via the sharedagentbox-claude-configvolume) had the user-side equivalent; cloud boxes had no such overlay. Policy precedence covers every provider. - Live verified on real E2B. Fresh
agentbox prepare --provider e2b, thencreatefrom a tiny scratch repo; in-box~/.agentbox-creds/{claude,codex,opencode}/carried the seeded files (vscode-owned, 600 mode, content matching~/.agentbox/<agent>-credentials.json); interactiveclaude --dangerously-skip-permissionslanded straight at the ready❯prompt — no "Bypass Permissions" accept screen, no theme picker.
Deferred follow-ups (intentional, not blocking)
- E2B checkpoints are full-state (disk + RAM), not disk-only — disk-only is
future work.
checkpoint createusesSandbox.createSnapshot, whichpauses the box and persists the entire VM state (memory + filesystem); booting a box from the checkpoint (Sandbox.create({ template: snapshotId })) resumes that paused state rather than booting fresh. Verified live (2026-06-23): a box created from a checkpoint kept the source box's running process (same PID), tmpfs//dev/shmcontents, frozenboot_id, and continuing uptime. The E2B SDK (2.27.1 and 2.30.5) exposes no disk-only snapshot option — the snapshot API takes only{ name }, and the model is inherently pause-based. This is mostly a cleanliness/fragility concern (a fresh box resumes the source's stale ctl/dockerd/agent processes), NOT a correctness blocker: the create-from-checkpoint flow re-seeds/workspacefrom the host git bundle (overlay delta), so the per-box branch still updates to the host's current tip — verified: a box from a checkpoint picked up an unpushed host-maincommit made after the checkpoint, viacheckpoint restore: /workspace: delta bundle (<oldTip>..<newTip>). A true disk-only checkpoint would needTemplate.build().fromTemplate(base).copy(<box state>)(boots fresh, like the base snapshot) — feasible (the SDK hasfromTemplate+copy) but the open question is what FS state to capture (E2B has nodocker commit-style rootfs diff), and it is slower to create than the seconds-fast memory snapshot. Tracked as future work; ships as full-state. - No
Template.list()in the SDK.prune --provider e2benumerates sandboxes only; built templates have to be removed from the E2B dashboard. Document this in the provider page; ship as-is. - 1-hour platform session cap on Hobby. The attach helper caps
timeoutMsat 55 minutes (seepackages/sandbox-e2b/src/attach-helper.ts- the staged
apps/cli/runtime/e2b/attach-helper.cjs) to leave headroom under the platform ceiling. NOTE: the box lifetime keepalive IS done — the host relay'scloud-keepaliveloop callsrenewTimeout→Sandbox.setTimeoutwhile the in-box agent is working, so a long agent run no longer dies at the create timeout (bounded by the plan's max session: ~1 h on Hobby). What's still capped is the interactive attach PTY itself (pty.create≤ 1 h on Hobby) — after 55 min the attach disconnects but the box stays alive and the agent keeps working; just reattach. Extending the live PTY mid-stream remains future work.
- the staged
- Drive harness display quirk on the e2b shell attach (Task 2 finding).
A plain
script -q -c …capture shows the prompt; the headless drive harness shows an empty screen. Underlying PTY bridge works. box.e2bTimeoutMsconfig key — DONE. Mirrorsbox.vercelTimeoutMs: the session timeout a new--provider e2bbox is created with (default 45 min), threaded viacloudSizingProviderOptions(apps/cli/src/lib/cloud-sizing.ts) intocreateand the agent-create commands, and recorded ascloud.sessionTimeoutMsso the keepalive seeds its tracked deadline precisely.- No
e2bNetworkPolicyconfig key — and it CAN be wired (backlog correction). An earlier note here claimed E2B's network knobs are "template-level (set atTemplate.build()time)" with "no per-create override." That is wrong: the SDK exposes them atSandbox.create()time, not build time. The create signature isSandbox.create(..., allowInternetAccess = true, network?: SandboxNetworkOpts, ...). OnlycpuCount/memoryMBare genuinely template-level (baked atTemplate.build()).- What a network policy covers (none of it is ports):
- Egress —
allowInternetAccess(defaulttrue): whether the box can reach the outside internet at all (the air-gapped-run lockdown). - Ingress —
network: { allowPublicTraffic: false }: makes the preview URL require ane2b-traffic-access-tokenheader instead of being open HTTPS (Task 1 §1 chose public to match Vercel). - Domain allow-listing lives under the same
SandboxNetworkOpts.
- Egress —
- Ports are unrelated. Any port is exposed on demand via
getHost(port)→{port}-{sandboxId}.e2b.app; there is no documented port cap and the network policy does not gate individual ports. AgentBox fixes the WebProxy at 8080. - Action if picked up: add a
box.e2bNetworkPolicy(egress on/off + public-traffic gating) create-time config key and thread it throughbackend.provision→Sandbox.create.
- What a network policy covers (none of it is ports):
box.imagecross-provider migration was already handled in Task 2 (per-providerbox.imageE2blives inpackages/config/src/types.ts).
Post-ship polish
- Clean prepare-gate error output (2026-06-03). The "no E2B base
template found" gate now throws
UserFacingError(from@agentbox/core) and the CLI's top-level catch renders it as a one-line message instead of a raw stack trace. Same treatment for the parallel vercel/hetzner gates. - Checkpoint manifest records
baseFingerprint(2026-06-03). The custome2bCheckpoint.create(mirrorsvercelCheckpoint.create) now passesbaseProvider,baseFingerprint, andcliVersiontowriteCloudCheckpointManifest— matching the scaffold default that daytona/hetzner inherit. Before, every e2b/vercel default checkpoint tripped the wizard's "captured before checkpoint versioning; base snapshot unverifiable" branch and stale-prompted forever; now the fresh-vs-stale verdict is real for all four clouds. - Login nudge + create-time base-freshness check (2026-06-03). Two paper-
cuts off the e2b first-run path:
agentbox e2b loginnow prints a one-line nudge pointing atagentbox prepare --provider e2bwhen no base template is recorded yet, so the login-only path doesn't silently leave the nextcreateto trip the (newly-clean) "no base template found" error.agentbox create --provider e2b/agentbox claude --provider e2bnow detect a stale base template at create time. Decision is made purely oncontextSha256— the SHA over the baked runtime files (CLI version strings are informational and never gate freshness, so a CLI patch that touches nothing baked staysfresh). TTY → merged "rebuild the base?" confirm;-y/non-TTY → loud warn + boot on the existing base (no auto-bake). Verified live: fresh-base TTY skips the prompt; mutating one hex char ofbase.contextSha256fires the prompt;-ymode warns and proceeds. Same plumbing covers daytona/hetzner/vercel via the newProvider.baseFingerprint?()capability.
Coordination notes (orchestrator)
- Each task is assigned to an agent via
agentbox claude -i "<prompt>" -- --permission-mode=plan(background box). The agent: plans → (orchestrator answers questions from the other providers) → implements → smoke-tests → opens a PR → fixes bugbot/CI → merges. Orchestrator then pullsmainand starts the next task. E2B_API_KEYis in.env.localand reachable inside every box; boxes run the relay and can launch boxes on other providers, so they can fully smoke-test.
Changelog
- 2026-07-10: Compose + buildx CLI plugins baked into the template
(
build-template.shdownloads the official release binaries into/usr/local/lib/docker/cli-plugins— bookworm's aptdocker-composeis the deprecated python v1, so no distro package). Also fixed the stalecustom-system-CLAUDE.md"No containers" paragraph (DinD has worked since 2026-06-23). Templates built before this need a re-prepare --provider e2b. - 2026-06-23: Fix: cloud
-ibackground jobs reporteddoneeven when the seeded agent session never came up (all cloud providers). The-iqueue worker's cloud path (runCloudJob→cloudAgentStartDetached→runDetachedinapps/cli/src/commands/_cloud-attach.ts) spawned the detached session-start helper withstdio: 'ignore'and resolved on any exit, discarding the exit code and stderr — so a helper failure (a transient SDK connect/exec error, the agent crashing at launch, or stale in-box credentials) was invisible and the job still wrotestatus: done. The only error that surfaced was the unrelated best-effortqueue.openInstep (e.g.herdr tab gave no pane id), which misled diagnosis toward openIn even though session start is independent of it. Fixes: (1)runDetachednow captures the exit code + stderr; (2)cloudAgentStartDetachedthrows (→ failed job with reason) when the session-create command exits non-zero; (3) newverifyDetachedSessionpollstmux has-sessionafter start — a session that's already gone means the agent exited immediately at launch (the exact "no tmux session running" symptom) — and best-effort-scrapes the pane for credential-rejection markers (Please run /login/API Error: 401/ …), failing the job with an actionableagentbox <agent> loginhint. Both callers handle the throw correctly: the queue worker marks the job failed;restoreAgentSessions(resume-on-restart) catches + logs. Cloud-wide (daytona/hetzner/vercel/e2b). Verified live on e2b: a job whose box claude creds had lapsed now reportsfailedwith the login hint instead of a falsedone; a healthy job still reportsdonewith a live session. Unit-tested intest/cloud-attach.test.ts. The seeded in-box claude OAuth token is a point-in-time snapshot that expires independently of the host session (seedocsnote on box-cred expiry) —agentbox claude loginre-seeds it; this fix makes that condition discoverable instead of silent. - 2026-06-23: Fix: cloud create with a relative
-wpath failed the git-clone workspace seed (all cloud providers).agentbox create --provider e2b -w ../repodied withgit clone … 'file://../repo' … fatal: '/repo' does not appear to be a git repository—file://URLs require an absolute path, so the relative one resolved to host/repo. The docker provider already doesresolve(opts.workspacePath); the cloudProvider.createdid not. Fixed by resolvingreq.workspacePathto absolute at the top ofcreateCloudProvider'screate(inpackages/sandbox-cloud/src/cloud-provider.ts), mirroring docker — which also keeps the box record'sworkspacePathcwd-independent. Found while testing e2b checkpoints; the fix is cloud-wide (daytona/hetzner/vercel/ e2b). Verified live:-w ../agentbox-test-repo-ghnow clones + completes, and the box record stores the resolved absolute path. - 2026-06-23: DinD shipped — docker baked into the base, auto-launched on
create/resume (mirrors vercel). Following the empirical verification below,
enabled in-box docker by: (1) adding a "docker engine (in-box DinD)" step to
packages/sandbox-e2b/scripts/build-template.sh(apt-get install docker.io iptables,groupadd -f docker,usermod -aG docker vscode, systemd docker disabled); (2) baking the sharedagentbox-dockerd-starthelper — added it toRUNTIME_ASSETSinruntime-assets.ts, thee2bFilesstage list inapps/cli/scripts/stage-runtime.mjs, and the install + trim steps inbuild-template.sh; (3) flippinglaunchDockerd: false → trueinpackages/sandbox-e2b/src/index.ts. The launch path itself (launchCloudDockerdDaemon, called on create + resume incloud-provider.ts) was already provider-agnostic. Like vercel, onlydocker.io+iptablesare installed (no fuse-overlayfs) —agentbox-dockerd-start's runtime probe picks overlay2, which works on E2B. Cost: ~+200MB base template, ~5-15s dockerd warm-up at create; always-on (not gated behind a config key). Pulled images + the running daemon carry across pause/resume (snapshots capture full disk+RAM). Docs synced: e2b.mdx, docker-in-docker.mdx, cloud-providers.md (intro table + §3c), CLAUDE.md. - 2026-06-23: DinD empirically verified to work on E2B — overturns the
Task 1 §4 "no nested containers" inference. Two-stage live test on the
user's E2B account:
- Raw
Sandbox.create()(vanilla E2Bbase): root hasCapEff: 000001ffffffffff(full set incl.cap_sys_admin),unshare --user/--mountboth rc=0, cgroup v2 mounted. Thenapt-get install docker.io→dockerd(Server 20.10.24, overlay2) →docker run hello-worldanddocker run alpine:3both succeeded; the container reports the host kernel6.1.158+. - Real agentbox e2b box (
agentbox create --provider e2b, bakedagentbox-basetemplate): as thevscodeuser (uid 1002, passwordless sudo)sudo apt install docker.io+sudo dockerd+sudo docker run hello-world|alpine:3all worked end-to-end. Conclusion: the Vercel "Firecracker → no DinD" reasoning does NOT transfer to E2B; the blocker on Vercel was its stripped capability set + seccomp, not Firecracker. AgentBox'slaunchDockerd: falseis a default (docker not baked in), not a platform limit. Filed "DinD on E2B" as an enable-able follow-up above. (Test note: when running this from inside an agentbox-in-agentbox, the in-boxgitshim rejects the create'sgit clone --no-checkoutseed step — put the real/usr/bin/gitahead of/usr/local/bin/giton PATH for the host-side create.)
- Raw
- 2026-06-23: Fix
agentbox e2b claudehanging on a blank screen at attach (thenagentbox attachworks). Root cause: the create→attach path runscloudAgentAttachwithextraArgs(always--dangerously-skip-permissions) and a new-terminalopenIn(iTerm2 split / tmux / cmux), which triggersstartDetachedSessionto pre-create the agent's tmux session before spawning the new pane (so the re-invoked attach, which carries no extraArgs, finds the session viatmux has-session).startDetachedSession→runDetachedspawns the provider'sdetachedattach argv and awaits its exit. On SSH/Vercel the detached argv runs the inner command (tmux new-session -d …; <config>) as the remote process and exits — but the E2B attach-helper always opens a persistent interactive in-box PTY shell and blocks onhandle.wait(); with no trailingexec tmux attachto replace the shell, it idles at a prompt forever, soawait startDetachedSessionnever returns and the CLI hangs right afterbox ready(no pane ever opens). Thetmux new-sessionhad already run, so the session + agent existed — which is why Ctrl+C thenagentbox attachrendered fine. Fix:buildE2bAttachnow appends--detachedto the helper argv whenopts.detachedis set, and the helper, in that mode, runs the inner command once via the non-interactivesb.commands.runand exits with its code instead of opening a PTY. Verified live: the detached helper now exits in ~1s (was: hung >30s) after creating the session + launching claude, andagentbox claude start <e2b-box> -- --dangerously-skip-permissionscompletes (Attached in new iTerm2 split.) in ~1s instead of hanging. Regression tests intest/build-attach.test.ts. - 2026-06-17: Fix
ERR_REQUIRE_ESMcrash on Node < 20.19 (e.g. 20.18, which ourengines.node >=20.10permits). Thee2bSDK ships noexportsmap (onlymain→CJS /module→ESM), so an ESMimport 'e2b'falls back to the CJSdist/index.js, whichrequire("chalk"). Its declaredchalk@^5.3.0resolves to the ESM-only chalk@5, and on Node < 20.19 (beforerequire(esm)was unflagged) thatrequirethrowsERR_REQUIRE_ESM— crashing the whole CLI at command registration (cli.ts→credentials.ts→sdk.ts→import 'e2b'), so everyagentboxcommand died, not just e2b ones. Fix: a scoped pnpm overridee2b>chalk: ^4.1.2in the rootpackage.jsonpins e2b's chalk to the CJS chalk@4 (samered/hex/gray/dimAPI e2b uses), sorequire("chalk")works on every supported Node. chalk is the only pure-ESM (norequirecondition) package e2b's CJS build requires — its other ESM deps (@bufbuild/protobuf,@connectrpc/*,openapi-fetch) are dual-package. The rest of the monorepo keeps chalk@5. Verified live on Node v20.18.0:import 'e2b',agentbox daytona login,agentbox e2b login --status, andagentbox --helpall load cleanly. Do not remove the override on a dep cleanup — newer e2b (2.30.1) still has the broken packaging. - 2026-06-03: Cold-attach "blank pane looks hung" fix (shared cloud path).
Symptom:
agentbox e2b claudereachedbox ready, showed the recap panel, then the attach sat on a featureless blank screen long enough that the user Ctrl+C'd; a lateragentbox claude attach <n>rendered fine. Investigated live against a running box and ruled out the usual suspects:- The PTY bridge is fine — attaching to an existing session paints the first byte in ~275ms and the full frame within seconds.
- tmux is fine — a session whose program delays its first draw correctly receives the frame the instant the program paints (no redraw bug).
- The dashboard probe is fine —
tmux list-sessions -F '#{session_name}'runs asvscodeand returnsclaude;agentbox listshowsclaude:working. The earlier "dashboard shows no agent session" was the same transient cold-start window (session not yet created / not yet drawn). - claude itself is fast on e2b — native-installer binary,
--version99ms, warm interactive first-paint ~750ms; box has free RAM, no swap. Root cause: the freshly-attached tmux client shows a blank pane during the agent's cold first-run (238MB binary fault-in + first-time config/plugin/skill/MCP init), which on the 2-vCPU microVM is several seconds with zero feedback. Fix:buildCloudAttachInnerCommand(apps/cli/src/commands/_cloud-attach.ts) now printsagentbox: starting <agent> (first paint may take a few seconds)...into the pane beforeexec-ing the agent, so the attach is never blank. Plain ASCII (no escape codes) so it survives every shell-quoting layer; the agent clears it on first draw. Applies to every cloud provider (e2b/vercel are the slowest cold-starters so they benefit most). Verified live: banner paints at ~380ms against a 6s slow-start stand-in. Tests intest/cloud-attach.test.ts.
- 2026-06-03: E2B usability pass — onboarding seed + shell exec + VNC + tag.
Live-verified end-to-end against
agentbox-base:latest.- Onboarding (the blocker): cloud creates were dropping into Claude's
first-run theme picker. Fixed by overlaying
~/.claude/_claude.jsonfrom the host's current~/.claude.jsonon every cloud create (across e2b/vercel/hetzner/daytona) — seepackages/sandbox-cloud/src/claude-json-overlay.ts. The default fallback (host has no~/.claude.json) now setshasCompletedOnboarding: true. - One-shot
agentbox shell:agentbox shell <cloud-box> -- cmdused to hang because the cloud path always opened a PTY attach viaprovider.buildAttach. One-shot now routes throughprovider.exec(the same primitive used at create-time); interactiveagentbox shellstill uses the PTY attach. - VNC: two issues — Debian 12 (E2B's base) doesn't ship
vncpasswd(Ubuntu-onlytigervnc-tools), and E2B's Python-venv websockify startup needs ~7-9s to bind. Fix: graceful-SecurityTypes Nonefallback inagentbox-vnc-startwhenvncpasswdis missing (signed preview URL is the access boundary, same effective model), and bump the cloud probe ceiling 5s → 15s invnc-launch.ts. Other providers (docker/hetzner/daytona/vercel) keep VncAuth. - Doubled
:latest:latest:preparestatus readtmpl agentbox-base:latest:latestbecauseinfo.name(already"agentbox-base:latest") got:${tag}re-appended. Now storesinfo.nameverbatim.
- Onboarding (the blocker): cloud creates were dropping into Claude's
first-run theme picker. Fixed by overlaying
- 2026-06-03: Task 3 done —
agentbox prune --provider e2blive-verified end-to-end (orphan spawned via the E2B SDK,--dry-runlisted it,-ydeleted it); doctor--providerhelp/error strings extended to include e2b; CLAUDE.md / docs/cloud-providers.md / docs/cloud-create-flow.md / README.md propagated to "five backends"; newapps/web/content/docs/e2b.mdxpage added with the Dockerfile/Template.build()differentiator front and center. Provider marked as shipped; deferred follow-ups (templates not enumerable, 55-min/1-hr session ceiling, drive-harness display quirk, noe2bTimeoutMs/e2bNetworkPolicyparallels) recorded above. - 2026-06-02: Backlog created; task breakdown + E2B↔CloudBackend mapping drafted.
- 2026-06-02: Task 1 done — package + CloudBackend core, smoke-tested end-to-end.
Open questions answered (preview URL is public by default; checkpoint deferred
to Task 2; webProxyPort=8080; no dockerd; resources are template-level).
Three cloud-scaffold gaps surfaced and fixed here: a Buffer→ArrayBuffer
conversion for
sb.files.write, a CommandExitError→CloudExecResult catch, and the carry root carve-out extended from vercel to e2b. - 2026-06-03: Task 2 done —
prepare(Template.build), interactive attach via the SDK's PTY API, andcheckpointviaSandbox.createSnapshot. Smoke-tested end-to-end against /tmp/e2bsmoke-repo. previewUrl no longer wakes paused boxes. Per-provider config keys (box.{image,defaultCheckpoint,size}E2b) added. Three SDK gotchas surfaced:template.copyrejects absolute paths (staged via temp fileContextPath);Sandbox.createauto-appends:defaultso we store the tagged${id}:${tag}; thee2bSDK must be external in apps/cli bundles (dynamicRequire onnode:url).