Send a herdr git worktree to a fresh E2B
cloud sandbox on demand — a snapshot upload of the live tree, uncommitted
changes and all (no push, no clone, no creds). Press prefix+shift+e in a
worktree to boot its sandbox and drop into a shell; the sandbox is torn down when you
remove the worktree.
…or drive it all from a dashboard (prefix+shift+d) — a live board of your
sandboxes; open one, work in its shell, and land right back on the board:
Creating a worktree does nothing by itself — you decide which worktrees go to the cloud. When you want one up:
prefix+shift+e (or: e2b-box open) ──▶ e2b-box provisions on the spot
│ marks the sandbox "provisioning"
▼
node provision.js (detached)
create E2B sandbox · metadata: herdrWorktreeKey=<folder>
upload the worktree (batched sandbox.files.write)
git init · record sandbox id + preview URL
│
spinner while booting ▼
`e2b sandbox connect <id>` ← shell in the sandbox
│ (type `exit` in the sandbox)
▼
on close: [p]ull changes down · [k]ill · [L]eave running
herdr worktree remove ──▶ worktree.removed event ──▶ teardown-worktree ──▶ e2b sandbox kill
Each worktree/folder gets its own sandbox, keyed by the folder's path. Nothing is auto-merged or pushed; the sandbox is scratch cloud compute that starts as an exact copy of your worktree.
- herdr ≥ 0.7.0, Node.js ≥ 22 (older Node trips the
e2bSDK's ESM imports; the plugin auto-resolves a newer Node if herdr runs on an older one), jq - E2B: the
@e2b/cli(e2bon PATH, for the sandbox shell) and an API key (dashboard). Provide the key either way:[secrets].e2b_api_keyin the plugin config (herdr-native, out of your shell profile and the repo, picked up by the running server — recommended), or- export
E2B_API_KEYin the env herdr launches from (wins if both set).
1. Install the plugin (the build step installs deps + links e2b-box onto PATH):
herdr plugin install tomasvarga/herdr-e2b
2. Set your E2B API key — get one, then either run
./install.sh once from a terminal (it prompts and saves it, hidden input,
chmod 600) or add it to the plugin config yourself:
# ~/.config/herdr/plugins/config/herdr-e2b/config.toml
[secrets]
e2b_api_key = "e2b_…"
(An E2B_API_KEY env var also works and wins if set.)
3. Bind a key to the open action — e.g. prefix+shift+e (avoid plain
prefix+e, which is herdr's edit_scrollback):
[[keys.command]]
key = "prefix+shift+e"
command = "herdr plugin action invoke open --plugin herdr-e2b"
Local dev:
herdr plugin link /path/to/herdr-e2b && ./install.sh.
Create worktrees the way you normally do — nothing happens until you send one up. In the worktree you want in the cloud:
e2b-box # provision (if needed) + open the sandbox shell (spinner while booting)
e2b-box up # provision in the background, don't attach
e2b-box shell # attach to an existing sandbox (won't create a fresh one)
e2b-box status # this worktree's sandbox record (status, template, sandbox id, url)
e2b-box list # every tracked sandbox
e2b-box url # preview URL (https://<port>-<id>.e2b.app)
e2b-box logs # tail provisioning progress
e2b-box sync # re-upload the current worktree into its sandbox (local → sandbox; provisions one if none yet)
e2b-box pull [--force] # download the sandbox's files back into this folder (sandbox → local)
e2b-box kill # kill this worktree's sandbox
status/list show the last known state — a sandbox marked ready may have
since paused or idle-timed-out on E2B's side; e2b-box open reconciles (and
reprovisions if it's gone).
e2b-box (no args) also works in a plain worktree that predates the plugin — it
provisions a sandbox on the spot.
The common verbs are also registered as herdr actions (open, sync,
pull, status, kill), so you can drive them without typing the CLI name.
They act on the focused pane's worktree. Invoke one directly:
herdr plugin action invoke sync --plugin herdr-e2b
or bind a key (optional) in your herdr config:
[[keys.command]]
key = "prefix+shift+e"
command = "herdr plugin action invoke open --plugin herdr-e2b"
open opens the interactive sandbox pane; the one-shot verbs print to the plugin
command log (herdr plugin log list --plugin herdr-e2b).
A live board of every tracked sandbox — status, sandbox id, files — with per-sandbox
actions and theming (shown above). Run e2b-dash, open the dashboard pane, or
invoke the dashboard action. Status is the last known state (it updates live
as the plugin provisions/syncs); open/Enter reconcile with E2B, and open
reprovisions a sandbox that has since idle-timed-out.
- Keys:
↑/↓move ·↵worktree ·oopen ·ssync ·ppull ·xkill ·rrefresh ·Ttheme ·qquit.sync/pull/killconfirm first and show the exact target worktree; each action runs against that sandbox's own worktree. Enterjumps to the sandbox's local worktree — focuses its herdr workspace if it's already open (no duplicate), else opens it. (oattaches the cloud shell;Entertakes you to where you edit.)openhands the pane to the sandbox shell, and on exit the dashboard offers pull / kill / leave.- Themes: defaults to your terminal's palette;
Tcyclesterminal · solarized-light · tokyo-night · dracula · nord · gruvbox(your choice is remembered). Set a default with[dashboard].themein the config. - No dev tools needed:
install.shuses a prebuilt binary (macOS universal; Linux x64/arm64, glibc) when one matches your platform, and only falls back to acargo buildfrom source otherwise. It's a single ~0.5–1 MB binary. (musl/Alpine or older glibc: build from source —cargois auto-detected. Rebuild the shipped binaries withtui/build-prebuilt.sh.)
File selection follows git: git ls-files --cached --others --exclude-standard
— tracked files (including your uncommitted edits) plus new untracked files,
honoring .gitignore. So build output, caches, node_modules, coverage, etc.
are not uploaded — only what git considers part of the repo. The files are sent
via the E2B SDK's files.write in batches; .git itself is skipped and the sandbox
runs git init -b <branch>. The [upload].ignore list is an extra safety filter
on top (keeps .env out even if tracked); for non-git folders it's the only
filter. Re-run e2b-box sync to push local changes up again.
Sandboxes default to base — E2B's minimal image, always available. Fine for
trying the flow, but tight on disk with no toolchain.
For real work, build a custom E2B template once — more disk + CPU, with your toolchain (node/pnpm/etc. or a coding agent) baked in. E2B fixes resources at build time, so a custom template is how you get a roomier sandbox that boots ready.
Quick setup — paste this to your coding agent (Claude Code, Codex, …) in this repo:
Set up a custom E2B sandbox template for me and wire it into herdr-e2b:
1. Create an `e2b.Dockerfile` FROM e2bdev/base with git, ripgrep, Node 22 + pnpm,
and my project's toolchain. Give it a roomy disk.
2. Run `e2b template build --name my-herdr-sandbox` (I'm logged in via `e2b auth login`).
3. Set `[sandbox].template = "my-herdr-sandbox"` in
~/.config/herdr/plugins/config/herdr-e2b/config.toml
Docs: https://e2b.dev/docs/sandbox-template
Or do it by hand — e2b template build --name my-herdr-sandbox (E2B's
template docs) — then point the config at it:
[sandbox]
template = "my-herdr-sandbox"E2B also ships public agent templates you can name directly (handy, though they can be tight on disk):
| Agent | Template | E2B docs |
|---|---|---|
| Claude Code | claude |
docs |
| Codex | codex |
docs |
| OpenCode | opencode |
docs |
| Amp | amp |
docs |
| Grok Build | grok |
docs |
Note the Claude Code template is named claude (not claude-code). Some
agents (e.g. Devin) have no prebuilt template — you install them into a
base sandbox yourself (or bake your own template), so there's nothing to name
here.
Route per branch with rules:
[[sandbox.template_rules]] # e.g. e2b/cx/* → Codex
pattern = "^e2b/cx/"
template = "codex"If a configured template isn't available, provisioning falls back to base with
a notification rather than failing.
Copy config/config.example.toml to
~/.config/herdr/plugins/config/herdr-e2b/config.toml. Everything has sane
defaults; set only what you want to change (template, timeout, project path,
preview port, upload batch size, ignore list).
- Sync is on-demand, not continuous.
e2b-box syncpushes local → sandbox;e2b-box pullbrings sandbox → local (git-aware, honors.gitignore).pullwrites only files that differ, reports each (+ new/~ overwrote), never deletes local-only files, and on a dirty or non-git tree prompts interactively or aborts when headless (pass--forceto overwrite). Review withgit diff. - Symlinks are skipped during upload.
- One sandbox per worktree/folder, keyed by the folder's path (same-named folders in different locations don't collide); the folder name is the display label.
- Removing a worktree kills its sandbox (cost control) — intentional.
- Sandboxes idle-time-out after
[sandbox].timeout_ms(default 1h, the free-tier cap).e2b-box opendetects a dead box and reprovisions. For longer sessions, bumptimeout_ms(paid plan) or set[sandbox].auto_pause = true— which works on the free tier and pauses (state preserved) instead of killing, so reconnecting resumes where you left off. That's the best way to keep in-sandbox work past the 1h cap.
MIT.

