Worktree Sandboxes (wt)

heyvm wt pairs a git worktree with a sandbox whose /workspace is that worktree. One command creates the branch + checkout + VM and drops you into a shell. Designed for the agent / parallel-branch workflow where you want to iterate on a change in isolation without stomping on the main working directory.

On macOS the default backend is apple_virt; on Linux it's libvirt/firecracker/bubblewrap depending on what's available.

Quickstart

# From inside any git repo. Creates ../<slugified-branch>, checks out
# the branch, boots a VM with that directory mounted at /workspace,
# drops you into an SSH shell.
heyvm wt feature/my-change

When you exit the shell, the VM shuts down. The worktree directory remains so you can re-enter or delete it with git worktree remove.

Detached mode (--detach)

If you want the VM to outlive the shell — for example, an agent working in the background, or a long build — add --detach:

heyvm wt feature/my-change --detach
# → spawns a small `heyvm __sandbox_owner` process that owns the VM
# → writes ~/.heyo/sandboxes/<id>/owner.json with the pid + guest IP
# → CLI returns in ~15s; VM keeps running

Reattach from any terminal:

heyvm wt feature/my-change --shell
# or by sandbox id:
heyvm sh sb-xxxxxxxx

Stop the VM:

heyvm stop sb-xxxxxxxx

Running heyvm wt feature/my-change --detach again for the same branch is idempotent — it reuses the live owner instead of spawning a duplicate.

Inspecting running sandboxes

heyvm wt --list           # only wt-*-named sandboxes
heyvm list                # all sandboxes, across backends
heyvm list --all          # include stopped
heyvm logs sb-xxxxxxxx    # tail ~/.heyo/sandboxes/<id>/owner.log
heyvm logs sb-xxxxxxxx -f # follow mode

Deploying a worktree to the cloud

Two variants. Both take the local worktree and spin up a cloud sandbox; they differ in what state carries over.

1. Workspace sync (default, libvirt + ubuntu:24.04)

Archives the worktree directory, uploads it, starts a cloud sandbox whose /workspace mirrors the local tree. No VM state sync — binaries you installed inside your local apple_virt sandbox don't carry over.

heyvm wt feature/x --deploy
# Optional: override driver/image/region/ttl:
heyvm wt feature/x --deploy \
  --deploy-driver libvirt \
  --deploy-image ubuntu:24.04 \
  --deploy-region US \
  --deploy-ttl-seconds 3600 \
  --deploy-port 8080

Output ends with Deployed successfully! ID: dep-xxxxxxxx. Use heyvm exec dep-xxxxxxxx <cmd> or heyvm sh dep-xxxxxxxx to interact. heyvm stop dep-xxxxxxxx tears it down.

2. Custom image deploy (--publish-image, firecracker)

Builds a full firecracker rootfs from a Dockerfile, publishes it to the cloud image registry, and deploys the worktree using that image. This is how you capture installed packages, apt/yum setup, config files — anything your Dockerfile expresses. Cross-arch (arm64 → amd64) is handled by docker buildx.

heyvm wt feature/x --deploy --publish-image \
  --deploy-dockerfile ./Dockerfile.firecracker \
  --deploy-image-name my-agent-image \
  --deploy-image-size-mb 4096

Requires Docker Desktop. The Dockerfile must produce a firecracker- bootable rootfs: see third-party/Dockerfile.firecracker-nginx for a working template (systemd-free init script at /init.sh, sshd on boot, etc).

Caveat: firecracker deploy-mode ignores the uploaded workspace archive, so /workspace is not auto-populated. If you want both a custom image AND workspace sync, add COPY . /workspace to your Dockerfile.

Custom apple_virt images (local)

To run a worktree sandbox on top of your own base image rather than the default Alpine Linux:

heyvm images build --backend-type apple_virt \
  --file ./my-base.Dockerfile \
  --name my-base \
  --size-mb 4096

heyvm wt feature/x --image my-base

The image lands at ~/.heyo/images/apple_virt/my-base/. Build requires Docker Desktop. If your Dockerfile installs linux-lts via apk, that kernel + initrd are used; otherwise the default image's kernel is injected into /boot/vmlinuz-lts and /boot/initramfs-lts.

See heyvm images build --help for full flag reference.

Command cheat sheet

CommandDoes
heyvm wt <branch>Create worktree + sandbox + attach shell
heyvm wt <branch> --detachSame, but background the VM and exit
heyvm wt <branch> --shellAttach shell to existing wt sandbox
heyvm wt --listList only wt-*-named sandboxes
heyvm stop <id>Stop the VM (detached or attached)
heyvm logs <id> [-f]Tail the owner process log
heyvm sh <id>SSH shell into any sandbox
heyvm exec <id> <cmd>Run a command, return output
heyvm wt <branch> --deployWorkspace-sync deploy to cloud (libvirt)
heyvm wt <branch> --deploy --publish-image ...Build custom image + deploy (firecracker)
heyvm images build --backend-type apple_virt ...Custom local base image

Troubleshooting

  • VM start failed: ... storage device attachment is invalid — the cached rootfs.img is corrupt. Remove the sandbox dir (rm -rf ~/.heyo/sandboxes/<id>) and let heyvm wt re-create it.
  • Shell attaches but doesn't respond — the guest's hvc0 shell isn't up. Check heyvm logs <id> for ssh ready / serial probe output. If --publish-image produced an image that never boots, rebuild it with the third-party/Dockerfile.firecracker-nginx template as a base.
  • heyvm sh <id> from a fresh terminal says "not found" — the wt name might have slashes that slugify unexpectedly. Use the full sandbox id (sb-xxxxxxxx) from heyvm wt --list.
  • --publish-image fails with "Not authenticated" — run heyvm login first, or set HEYO_ARCHIVE_TOKEN.