cd /news/ai-agents/show-hn-boxhaven-run-claude-code-and… · home › topics › ai-agents › article
[ARTICLE · art-139234] src=github.com ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

Show HN: BoxHaven – Run Claude Code and Codex on Persistent Cloud VMs

BoxHaven launched as a hosted and self-hostable service that runs AI coding agents such as Claude Code, Codex, and Gemini on persistent remote Linux boxes with managed tmux sessions, project sync, and direct SSH access. The CLI requires `bh` 0.2.0 or later, and a public BoxHaven skill installs via Vercel's Skills CLI with `npx skills add finbarr/boxhaven --skill boxhaven -g -a codex claude-code` on Node.js 22.20 or later. The tool targets teams assembling remote dev boxes by hand, letting agents keep working after a laptop disconnects and allowing parallel agent runs on separate boxes.

read15 min views1 publishedSep 24, 2026
Show HN: BoxHaven – Run Claude Code and Codex on Persistent Cloud VMs
Image: Michielbdejong (auto-discovered)

BoxHaven gives development teams a standard way to run AI coding agents and developer shells on named remote Linux boxes. Try the hosted version or self-host it with your own cloud credentials. Each box keeps running after a laptop disconnects, has a managed tmux session for long-running Codex, Claude, Gemini, or shell work, and can sync the current project to and from the remote machine.

BoxHaven is built for the workflow many teams are assembling by hand today: remote dev boxes for individual developers, persistent agent sessions, direct SSH access, project sync, GitHub pushes from the box, and a self-hostable control plane.

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.

The CLI is intentionally small, and the workflow is agent-first: copy your project to a box once, start Claude or Codex inside the box's tmux session — resuming your local conversation if you like — then disconnect and let it work:

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    # claude starts working in the box's tmux session
bh connect work

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 that exact session 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. The "Continue with GitHub" button appears only when the operator configures a GitHub OAuth app (GITHUB_CLIENT_ID/ GITHUB_CLIENT_SECRET). GitHub's provider-verified email does not need a second verification step; bh login then works the same either way.

Run agents in parallel, each on its own box. Redirecting input and output makes these launches detached even when you run this example in a terminal:

launch_logs=$(mktemp -d)
(bh create work-2 && bh run work-2 codex 'Review the project and report improvements.') </dev/null >"$launch_logs/work-2.log" 2>&1 &
codex_pid=$!
(bh create work-3 && bh run work-3 claude 'Run the project checks and report failures.') </dev/null >"$launch_logs/work-3.log" 2>&1 &
claude_pid=$!
wait "$codex_pid" || cat "$launch_logs/work-2.log"
wait "$claude_pid" || cat "$launch_logs/work-3.log"
bh connect work-2

Teach your local Codex or Claude agent to operate BoxHaven with the public BoxHaven skill. Install it using Vercel's Skills CLI, the installer behind skills.sh (Node.js 22.20 or later):

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

Invoke $boxhaven in Codex or /boxhaven in Claude. The skill teaches box creation, persistent sessions, parallel launches, progress and preview checks, .boxhavenignore, and retrieving results. It bundles a parallel launcher and sync reference, and links to current docs for details. It requires bh 0.2.0 or later and your existing login.

Update it with npx skills update boxhaven -g. Installation, project scope, and version pins are documented on the site. Inside a remote box, a separate image-installed boxhaven-web-preview skill describes its web preview setup.

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.

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.

  • 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 orGITHUB_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.

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.

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.

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.

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

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 orGITHUB_TOKEN for GitHub repository access inside remote boxes

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 : defaultnyc3 .
  • DIGITALOCEAN_SIZE : defaults-2vcpu-4gb .
  • DIGITALOCEAN_IMAGE : base image fallback, defaultubuntu-24-04-x64 .
  • BOXHAVEN_REMOTE_IMAGE_DIGITALOCEAN orBOXHAVEN_REMOTE_IMAGE : golden snapshot id for new boxes.

Hetzner Cloud:

  • HCLOUD_TOKEN : API token, enables the provider.
  • HETZNER_LOCATION : defaultnbg1 (alsofsn1 ,hel1 ,sin ).
  • HETZNER_SERVER_TYPE : provider plan behind the built-insmall size, defaultcpx22 .
  • HETZNER_IMAGE : base image fallback, defaultubuntu-24.04 .
  • BOXHAVEN_REMOTE_IMAGE_HETZNER : golden snapshot id for new boxes.

Every provider supplies the built-in small, medium, and large sizes. 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.

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:

bh team create acme

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

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

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.

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, normally80 .
  • BOXHAVEN_WEB_BIND : the bind address to use, normally0.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.

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

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.

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.

── more in #ai-agents 4 stories · sorted by recency
── more on @boxhaven 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-boxhaven-run…] indexed:0 read:15min 2026-09-24 · —