GitHub - finbarr/boxhaven: Persistent remote dev machines for AI agents

GitHub

16 min read Original article ↗

BoxHaven gives your coding agents separate Linux VMs to work in parallel. Try the hosted version or self-host it with your own cloud credentials. Each agent gets its own project copy, compute, and persistent session, so independent tasks can run at once without competing for your laptop's resources. Agents keep working after you disconnect, too.

Start With The Agent Skill

Install the BoxHaven skill for your local Codex or Claude agent using Vercel's Skills CLI (Node.js 22.20 or later):

npx skills add finbarr/boxhaven --skill boxhaven -g -a codex claude-code

The skill uses bh 0.2.0 or later and your BoxHaven login. Complete the one-time CLI setup before your first task. Open a new agent session if the skill is not visible, then invoke $boxhaven in Codex or /boxhaven in Claude with a request:

Work in parallel

Use BoxHaven to run a code review and a test coverage audit on two separate VMs in parallel. Check both agents' progress and bring back their findings.

Keep working while you're away

Use BoxHaven to continue this task on a remote VM so I can close my laptop. Check that the agent is working and give me the command to reconnect.

The skill teaches box creation, persistent sessions, parallel launches, progress and preview checks, .boxhavenignore, and retrieving results. Your local agent manages the boxes while you review the work. Each VM is billed while it exists; ask your agent to retrieve the results and destroy finished boxes when you're done.

Update with npx skills update boxhaven -g. See the skill guide for project installation, version pins, and batch examples. Inside a remote box, a separate image-installed boxhaven-web-preview skill describes its web preview setup.

Desktop App

The BoxHaven desktop app shows your boxes in a sidebar and attaches to the selected box's persistent tmux session. It uses your existing CLI login and works with hosted or self-hosted backends. Click New box to choose a provider, region, and size with price estimates, then open the new box’s terminal automatically. Rename from the sidebar overflow menu without disconnecting. Open its web preview from the header, or use the overflow menu to destroy it with confirmation.

Download BoxHaven for macOS Apple Silicon. Open the signed, Apple-notarized DMG, drag BoxHaven to Applications, and sign in. The CLI is bundled; no development tools are required. Updates download in the background and prompt before restarting. See the desktop guide for installation, updates, and source builds.

Standalone CLI updates: bh upgrade for direct installs, or brew update && brew upgrade boxhaven for Homebrew installs.

Use The CLI Directly

From your project directory, after installing bh:

bh login
bh ssh-config install # one-time setup for normal ssh/scp aliases
bh create work        # provisions a box and syncs this project once
bh run work claude    # starts Claude in a persistent session
# Ctrl-b, then d disconnects; the agent keeps running.
bh connect work       # reattach whenever you like

On first login, enter your backend API URL, or press Enter to choose hosted BoxHaven. Later logins reuse the saved URL. For scripts or a noninteractive terminal, pass --backend-url or set BOXHAVEN_BACKEND_URL.

Mid-conversation with Claude locally? bh run work claude --continue resumes your forwarded conversation on the box.

Sign up with email and password, then follow the one-hour verification link. The link verifies your email and signs you in automatically, replacing any other account open in that browser. GitHub sign-in is available when the operator configures a GitHub OAuth app; bh login works the same either way.

For manual parallel launches, see the agent skill guide.

bh create asks the backend for a machine, waits for it to be reachable, and syncs the current project into /opt/boxhaven/project. After that the box owns its copy: bh run does not mirror local files, so nothing an agent does on the box is ever overwritten by a routine command — bh sync up pushes local changes explicitly (mirroring deletions) and bh sync down retrieves the box's work. Project sync excludes common dependency/cache directories such as node_modules/, .next/, and .venv/ by default. Add a .boxhavenignore file at the project root for additional rsync-style exclude patterns. Sync completion reports elapsed time, network bytes, changed bytes, and file counts.

When you start claude or codex with bh run, bh forwards your newest local sessions for the project, so claude --continue on the box picks up the conversation exactly where your laptop left it. Agent commands use the machine's managed tmux session: they attach with a terminal and start detached without one. Other commands run over direct SSH. Inspect session output before calling an agent working; a successful detached launch is not task completion.

Direct SSH

Run the one-time setup after logging in to add managed bh-<name> aliases to your normal OpenSSH configuration:

bh ssh-config install
ssh bh-work
scp ./notes.txt bh-work:/opt/boxhaven/project/
rsync -az ./fixtures/ bh-work:/opt/boxhaven/project/fixtures/

The aliases also work in tools that use OpenSSH configuration, including VS Code Remote SSH. Box aliases update after create, list, status, rename, and destroy operations; bh ssh-config refresh forces an update. Each connection transparently replaces the local short-lived certificate while reusing a device key stored under ~/.boxhaven/ssh. Remove the managed include with bh ssh-config uninstall.

What It Provides

  • Named remote boxes: create, list, inspect, connect, sync, run, rename, and destroy.
  • Project sync to /opt/boxhaven/project with explicit sync up/down commands.
  • A managed tmux session per box for long-running AI agent sessions.
  • Mouse-wheel scrolling through tmux history in interactive sessions.
  • Direct SSH using backend-signed short-lived user certificates.
  • GitHub HTTPS credential forwarding from local GH_TOKEN or GITHUB_TOKEN.
  • Git safe-directory configuration for the synced project path.
  • Optional preview hostnames for HTTP services running on the box.
  • Multiple cloud providers per backend: DigitalOcean and Hetzner Cloud.
  • Provider plan discovery with team-owned size shortcuts.
  • Team-owned boxes with roles, shareable invite links, and per-team visibility.
  • Admin-managed golden images that become the default for new boxes.
  • An open-source Fastify/Better Auth backend.

Docs

Agents can discover the documentation through llms.txt. Each page also has a Markdown URL, such as commands.md, generated from the same source on every docs build.

License

BoxHaven is open source under the GNU Affero General Public License v3.0 only (AGPL-3.0-only). See NOTICE and LICENSE for the copyright notice and full license text.

Self-Hosting

The web console, API, and documentation are separate surfaces. The backend serves the console and API, while docs builds as a static site. Set BOXHAVEN_DOCS_URL when building the console if its footer should link to a different documentation site. Backend distributions can add in-process modules through the published @boxhaven/backend contract.

The self-hosted console checks the public BoxHaven GitHub release through its backend and shows a compact release banner when the installation is behind. Release checks are cached and disappear silently when GitHub is unreachable.

Run the complete open-source backend with your own provider credentials and no built-in limits. No BoxHaven subscription or company account is required. See backend/README.md for running the backend and deploy for the deployment bundle. Your cloud provider charges for the machines you create.

Install

One-liner (macOS and Linux, installs the latest release):

curl -fsSL https://raw.githubusercontent.com/finbarr/boxhaven/master/install.sh | sh

Homebrew (via the finbarr/tap tap):

brew install finbarr/tap/boxhaven

Or build from source:

go build -o bh ./cmd/bh
./bh version

Normal CLI commands check the public BoxHaven GitHub releases in the background. Results are cached for 24 hours under the BoxHaven config directory, so update notices never wait on the network and offline checks stay silent. When a newer release is known, bh prints one concise link after reading the cached result.

Then jump straight into the quickstart:

bh login
bh ssh-config install # one-time setup for normal ssh/scp aliases
bh create work       # provisions the box and syncs this project once
bh run work claude   # start Claude in the box's tmux session
bh connect work      # reattach after disconnecting

Configuration

BoxHaven reads global config from ~/.config/boxhaven/config.toml and project config from .boxhaven.toml.

[remote]
backend_url = "https://api.example.com"
token = "browser-granted-session-token"
ssh_user = "boxhaven"
provider = "hetzner"
setup = [
  "docker compose up -d db"
]

Environment overrides:

  • BOXHAVEN_BACKEND_URL
  • BOXHAVEN_TOKEN
  • GH_TOKEN or GITHUB_TOKEN for GitHub repository access inside remote boxes

Providers

A single backend can serve multiple cloud providers. GET /v1/providers lists what a backend has configured, and bh create picks the backend default unless a provider is requested explicitly:

bh create work --provider hetzner
bh create work --provider digitalocean --region sfo3
bh create work --provider hetzner --region fsn1 --image 12345678

--region is passed through to the provider. --image accepts a name or ID from the target team’s image list. Set a project-wide default with the provider key under [remote] in .boxhaven.toml or the global config.

The backend enables a provider when its credentials are present and selects the default with BOXHAVEN_BACKEND_PROVIDER. When unset, the first configured provider is the default (DigitalOcean when both are configured).

DigitalOcean:

  • DIGITALOCEAN_ACCESS_TOKEN: API token, enables the provider.
  • DIGITALOCEAN_REGION: default nyc3.
  • DIGITALOCEAN_SIZE: default s-2vcpu-4gb.
  • DIGITALOCEAN_IMAGE: base image fallback, default ubuntu-24-04-x64.
  • BOXHAVEN_REMOTE_IMAGE_DIGITALOCEAN or BOXHAVEN_REMOTE_IMAGE: golden snapshot id for new boxes.

Hetzner Cloud:

  • HCLOUD_TOKEN: API token, enables the provider.
  • HETZNER_LOCATION: default nbg1 (also fsn1, hel1, sin).
  • HETZNER_SERVER_TYPE: provider plan behind the built-in small size, default cpx22.
  • HETZNER_IMAGE: base image fallback, default ubuntu-24.04.
  • BOXHAVEN_REMOTE_IMAGE_HETZNER: golden snapshot id for new boxes.

Sizes

Every provider supplies the built-in small, medium, and large sizes. Add --json to bh size list or bh size plans for machine-readable provider, size, region, and pricing data. Both default to your configured provider.

Use bh size list to see their exact CPU, memory, disk, provider plan, and effective hourly price with day and month estimates. bh size plans shows the same price context before an owner or admin gives a provider plan a team-owned shortcut:

bh size plans --provider digitalocean
bh size create gpu --provider digitalocean --plan gpu-4000adax1-20gb
bh create model-work --size gpu
bh size rm gpu

Shortcuts store a provider and provider-plan slug. Creating a box resolves the shortcut once and records the resolved plan on the box; changing or deleting a shortcut never changes an existing machine.

Teams

Every box belongs to a team. Each account automatically gets a default team named <name>'s team, so boxes work with no setup. Create another team in the console or from the CLI:

Invite teammates by shareable link. bh team invite <email> (or the console Teams view) creates an invitation and prints an invite URL such as https://app.example.com/invite?id=<invitation-id>; send that link to the teammate, who accepts it after signing in with the invited email address. BoxHaven does not send invitation emails.

New boxes land in the session's active team: bh login pins it, and accepting an invitation, creating a team, or selecting a team in the console's Team view switches it for that session. Control placement explicitly:

bh create work --team acme   # create a box directly in a team
bh team switch acme          # change the CLI default team for new boxes
bh move work acme            # move one of your boxes to another of your teams

Members have one of three roles: owner, admin, or member. Team members see exactly the boxes in that team and who owns each one; boxes in your other teams stay invisible to them. Owners and admins can destroy team boxes; members can only destroy their own.

When you leave a team (or are removed), your boxes in it move back to your active team the next time you list them; until that next listing, the old team can still see and destroy them.

Only an owner can delete a team. Deletion is refused until every box in the team has been destroyed and every in-progress create has finished. Failed boxes still require explicit destruction because BoxHaven does not assume provider cleanup succeeded. If the backend restarts during creation, its stale reservation is cleared and the durable recovery box remains for that explicit cleanup. Provider discovery updates the record for cleanup but never makes it ready; bh list reports recovery required, and the box must be destroyed and created again. A deployment-specific module can add another fail-closed check for external account state.

Moving or sharing never copies a box. To hand a teammate a box like yours, snapshot it into a team image and create a new box from the resulting image:

bh image create work --name dev-tools
bh create work-clone --image dev-tools

Images

Golden images carry the BoxHaven VM runtime so new boxes boot ready to use. Image names are unique within the active team, with no added prefix. Different teams can independently use the same name; images remain private to their owning team. Names must start with a letter or number after normalization. A team member can snapshot one of the team's boxes, then select that image when creating another box in the same team. If no image is selected, BoxHaven uses the backend's configured default image for that provider.

bh image ls
bh image create work --name dev-tools
bh create work-clone --image dev-tools
bh image rm dev-tools --force

GitHub Repository Access

When the current project's origin, or the origin of an immediate child repository in a multi-repository workspace, points at GitHub, bh create, bh run, bh connect, and bh sync up forward GitHub auth to the remote box in /run/boxhaven/session.env. The file lives in tmpfs, is readable only by the remote SSH user and root, and is replaced or removed by the CLI on the next command. The remote image includes a Git credential helper that uses those variables, so agents and shells inside the box can push to HTTPS GitHub remotes.

GH_TOKEN or GITHUB_TOKEN are used when set. Otherwise, if the GitHub CLI is installed and authenticated locally, bh uses gh auth token and forwards that token for the remote session. For the smoothest agent workflow, use an HTTPS GitHub origin and either export a token with the repository scopes your team allows or run gh auth login before starting the remote session.

bh create, bh run, bh connect, and bh sync up also forward selected local agent login files for Claude, Codex, Gemini, GitHub Copilot, and opencode (for example ~/.codex/auth.json, ~/.claude.json, ~/.claude/.credentials.json on Linux, and ~/.claude/settings.json). Recent claude/codex sessions for the current project are forwarded when you start those agents so they can resume; broader histories, caches, and databases are never copied.

Those commands also forward the effective local Git author identity for the current project by setting user.name and user.email in the remote SSH user's global Git config. BoxHaven does not copy the full local Git config.

Web Preview

Open a box's Public preview link in the console, or use Open preview in its details drawer. Previews open in a new tab. Each box has a small character inspired by the logo; its appearance stays the same across renames and team moves.

Each box receives a public preview URL when the backend is configured with a preview base domain. The backend warms the preview URL during machine create so Caddy has already completed on-demand certificate issuance before the URL is shown. Public HTTPS and WebSocket traffic terminate at the BoxHaven control plane, then the backend proxies plain HTTP/WebSocket traffic to the machine's BOXHAVEN_PREVIEW_TARGET_PORT, default 80.

Inside the box, commands receive:

  • BOXHAVEN_PREVIEW_URL: the browser URL to share.
  • BOXHAVEN_PREVIEW_HOSTNAME: the public hostname.
  • BOXHAVEN_PREVIEW_TARGET_PORT / BOXHAVEN_WEB_PORT: the machine port to serve, normally 80.
  • BOXHAVEN_WEB_BIND: the bind address to use, normally 0.0.0.0.
  • /run/boxhaven/context.json: structured runtime context with the same preview details under .preview.

Apps should bind HTTP to 0.0.0.0:$BOXHAVEN_WEB_PORT or run a reverse proxy on that port to the app's internal dev-server port. Framework dev-server WebSockets, including Vite HMR, use the same preview URL. The default boxhaven user has sudo access if binding to port 80 is required.

Backend

The open-source backend in backend provides:

  • Better Auth browser/device login
  • team-centric box ownership with automatic default teams
  • DigitalOcean and Hetzner Cloud provisioning
  • multi-member teams via Better Auth organizations with roles and invite links
  • admin-managed golden images per provider
  • backend-signed short-lived SSH certificates
  • VM agent RPC for setup commands and tmux session lifecycle
  • generated preview hostnames and a browser console/auth app

Run it locally:

cd backend
npm ci
BETTER_AUTH_SECRET="$(openssl rand -hex 32)" \
DIGITALOCEAN_ACCESS_TOKEN=dop_v1_example \
npm run dev

Then point the CLI at it:

bh login --backend-url http://127.0.0.1:8787
bh create work

DigitalOcean Deployment

Production deployment and golden-image tooling live in deploy/digitalocean. Deploy the public self-hosted stack from the repository root with:

npm run deploy:app -- --target root@app.example.com

The SSH target is required: use -- --target user@host or set BOXHAVEN_DEPLOY_TARGET. The command fast-forwards /opt/boxhaven/app on master, builds the docs site, and runs the Compose deploy on that machine. Health checks use BOXHAVEN_API_URL, BOXHAVEN_APP_URL, and BOXHAVEN_DOCS_URL from its Compose environment. The remote VM snapshot is rebuilt separately. On the Droplet itself, use npm run deploy:production:local.

The deploy script supports distribution-specific build and service wiring through BOXHAVEN_PRODUCTION_COMPOSE_OVERLAY_FILE and BOXHAVEN_PRODUCTION_COMPOSE_OVERLAY_ENV_FILE (or the matching --compose-overlay flags). It refuses to deploy or verify an existing distribution without an overlay when the backend's Compose metadata records one.

After changing the VM runtime or image-builder code, explicitly rebuild and publish the remote VM image:

npm run deploy:runtime -- --target root@app.example.com

The runtime deploy creates and snapshots a temporary DigitalOcean builder Droplet, updates BOXHAVEN_REMOTE_IMAGE, then restarts and verifies the backend so new boxes use the image. When an active BOXHAVEN_REMOTE_IMAGE exists, the builder starts from that snapshot by default instead of reinstalling the full OS/toolchain from Ubuntu. Use npm run deploy:runtime -- --target root@app.example.com --full-base-image only for base OS or runtime dependency rebuilds.

Both deploy commands forward your SSH agent for Git access. Set BOXHAVEN_DEPLOY_DIR if the server checkout is not /opt/boxhaven/app.

Production Smoke

Run the reusable remote lifecycle smoke against your test backend after remote VM, SSH, sync, snapshot, or agent changes:

BOXHAVEN_SMOKE_BACKEND_URL=https://api.example.com \
BOXHAVEN_TOKEN=... \
GH_TOKEN=... \
BOXHAVEN_SMOKE_GIT_REMOTE=https://github.com/<org>/<smoke-repo>.git \
make smoke-remote

The default smoke is intentionally fast: it creates one box from the active snapshot, syncs a temporary Git project, verifies runtime tools, fetches the preview URL, optionally pushes and deletes a temporary GitHub smoke branch, and destroys the box unless BOXHAVEN_SMOKE_KEEP=1 is set.

Use make smoke-remote-full with BOXHAVEN_SMOKE_RESTART_BACKEND_CMD when the agent reconnect path needs coverage. Use make smoke-remote-two-box only for concurrency, provider import, or multiple-machine behavior.

Version compatibility

Product versions can advance without changing the wire protocol. Clients send X-BoxHaven-Protocol: 1; the backend advertises the supported protocol at /v1/compatibility and rejects a different protocol with HTTP 426 and upgrade instructions. Clients and images predating negotiation speak the original protocol 1. Deploy the backend before clients when making an incompatible change.

VM agents report their protocol and an SHA-256 fingerprint of their agent code. bh status NAME and machine JSON expose runtime_protocol and runtime_version; older agents show an unreported fingerprint. Incompatible agent RPC is blocked without stopping the VM or its running processes. A golden-image rotation affects new boxes only. Preserve existing work, create a replacement from the new image, and verify it before destroying the old box. BoxHaven does not rewrite running VMs during a backend or desktop update.

Self-hosted core installations can use versioned backend images and npm run upgrade:backend -- --version v0.4.0 --env-file .env for a backed-up, health-checked upgrade. See the upgrade and rollback guide.