toolsdevopsai

The shape of the system

Crabbox is a remote execution surface with a deliberately small control plane. For Osprey Strike, the Crabbox setup in infrastructure/crabbox/ uses the Mac Studio as the Docker host and the developer laptop as the command center. There is no coordinator service in this path, no Cloudflare tunnel, and no AWS dependency. The laptop runs crabbox; Crabbox invokes an external provider script; that script SSHes to qs-mac-studio; Docker starts a container; Crabbox then syncs and executes against the container over SSH.

flowchart LR
  Laptop[Developer laptop] --> CLI[crabbox CLI]
  CLI --> Provider[studio-docker-provider]
  Provider -->|SSH over Tailscale| Studio[qs-mac-studio]
  Studio --> Docker[Docker]
  Docker --> Box[per-lease Linux container]
  CLI -->|rsync + SSH over Tailscale| Box

The important word is per-lease. Two branches, or two developers, do not share a working directory on the Studio. Each warm box gets its own container, its own synced checkout of the current branch, its own processes, and, in app mode, its own inner Docker data volume. That makes the box closer to an ephemeral CI worker than to a shared dev server.

Key Takeaway

Every box spun up this way starts from the branch you are on when Crabbox syncs. If you want to test a different branch, switch branches locally and create or target the corresponding box deliberately.

Setup is split between the laptop and the Studio

The laptop setup proves reachability. A developer needs the Crabbox CLI, Tailscale access to the tailnet containing qs-mac-studio, and SSH access to the Studio account that can run Docker. The source README calls out the one-time checks: tailscale status, ping qs-mac-studio, and ssh <studio_ssh_user>@qs-mac-studio echo ok.

Laptop preflight
brew install openclaw/tap/crabbox
tailscale status | grep qs-mac-studio
ssh <studio_ssh_user>@qs-mac-studio echo ok

The Studio setup has two durable pieces. build-runner.sh ships the runner build context to the Studio and overlays the live repo-root .mise.toml, so the image bakes the project toolchain from the same source developers use locally. install-reaper.sh installs an hourly launchd job that runs studio-reap.sh, removing orphaned containers that outlive their hard cap.

Studio image and reaper
cd infrastructure/crabbox
./build-runner.sh <studio_ssh_user>@qs-mac-studio
./install-reaper.sh <studio_ssh_user>@qs-mac-studio

The repo-root crabbox.yaml chooses the external provider and passes the Studio-specific settings into studio-docker-provider: the host, SSH user, runner image, container user, persistent laptop keypair path, Docker binary path, and max_lifetime_secs. The app-stack path also needs privileged: true, because the container must run its own Docker daemon for the Osprey compose stack.

Config skeleton
provider: external

external:
  command: /absolute/path/to/osprey-strike/infrastructure/crabbox/studio-docker-provider
  workRoot: /work/crabbox
  config:
    studio_host: qs-mac-studio
    studio_ssh_user: q
    image: crabbox-studio-runner:latest
    container_user: crabbox
    ssh_key: ~/.config/crabbox-studio/id_ed25519
    max_lifetime_secs: 28800
    docker: /usr/local/bin/docker
    privileged: true
Warning

privileged: true is chosen when the box is created, not per command. Keep a plain test config for gates and a privileged app config for full-stack work if you want both modes without editing one file back and forth.

Pure CLI work is the base layer

The first useful mode does not need Claude Code or repo helpers. Once config is in place, plain Crabbox commands give you the normal lifecycle: check the provider, warm a box, run a command, SSH into it, and release it.

Pure Crabbox lifecycle
crabbox doctor
crabbox warmup
crabbox run -- just api check
crabbox ssh
crabbox release

When there is more than one ready box, make the slug explicit. This is the habit that keeps parallel branch work sane.

Target one branch box
crabbox list
crabbox run --id <slug> -- just api check
crabbox run --id <slug> -- just mcp check
crabbox ssh --id <slug>

The provider makes this possible by returning normal SSH coordinates to Crabbox: user, host, published port, and key path. The container’s sshd is published on an ephemeral port on the Studio host, bound to a Tailscale-reachable address. Crabbox handles the sync and command execution after that; the provider is only the lifecycle adapter.

Tip

Level 1 work is multi-box friendly. Running gates on several branches is just a matter of keeping the slugs straight and passing --id <slug>.

Osprey adds app helpers

The Osprey helper layer starts where plain command execution stops. The infrastructure/crabbox/justfile is meant to be imported from the repo root so operators can list boxes, ensure one exists, capture screenshots, and open browser tunnels without remembering the lower-level SSH plumbing.

Repo helper commands
just crabbox ensure
just crabbox list
just crabbox screenshot <slug>
just crabbox app-tunnel <slug>

just crabbox ensure prints the slug of a ready box, reusing one if possible unless --fresh is passed. It deliberately does not start the app stack. That separation matters: a ready box means Crabbox can SSH to it; an app-ready box means just dev-headless has already brought up the Osprey services inside it.

For app work, the normal sequence is to ensure or choose a box, run the full stack inside that box, then use either screenshot automation or an SSH tunnel.

Full-stack box
slug=$(just crabbox ensure)
crabbox run --id "$slug" -- just dev-headless
SMOKE_TARGET=/admin/tenants just crabbox screenshot "$slug"

The screenshot path runs Playwright inside the box against http://localhost:4700, logs in through Keycloak as a seeded user such as sysadmin/sysadmin, navigates to the target route, writes a PNG in the box, then uses the SSH details from crabbox ssh --id <slug> to copy the file back. By default, outputs land under /tmp/screenshots/ rather than in the repo tree.

Key Takeaway

Level 2 is also multi-box capable. You can keep several full-stack boxes alive, but commands should name the target slug because auto-selection only stays safe when exactly one ready box exists.

Claude Code uses crabbox-it

Claude Code should use Crabbox as a remote workbench, not as a mystery side channel. The crabbox-it skill is the right interface when the agent is already inside a coding task and needs an isolated box to run gates, boot the stack, inspect a page, or capture a screenshot.

The useful contract for crabbox-it is simple:

  1. Ensure or reuse a Crabbox for the current branch.
  2. Run the requested command with an explicit slug.
  3. If app inspection is needed, start just dev-headless in the box.
  4. Use just crabbox screenshot <slug> for agent-readable visual proof.
  5. Report the slug, command, result, and any captured artifact.

That wrapper is valuable because it keeps the agent from mixing local host state with branch-specific app state. The box is still just a Crabbox. It still contains the current branch clone. The difference is that Claude Code can treat it as a repeatable environment instead of hand-assembling the lifecycle every time.

Claude Code shape
Use crabbox-it to run the API gate for this branch.
Use crabbox-it to boot the app and capture /admin/tenants.
Use crabbox-it to verify the fix in a fresh box.
Tip

Use the Claude Code path for tasks where the agent needs to iterate. Use pure CLI when a human operator already knows the exact command and only wants the box as remote execution capacity.

There are three interaction levels

Crabbox becomes easier to reason about when the interaction levels are explicit. The same box can support different work, but the concurrency story changes as you climb the ladder.

Level What you do Example Multiple boxes?
1 Run gates and tests crabbox run --id <slug> -- just api check Yes. This is the best multi-branch mode.
2 Run the full stack and automate screenshots crabbox run --id <slug> -- just dev-headless; just crabbox screenshot <slug> Yes, within Studio resources. Always name slugs.
3 Open local browser tunnels just crabbox app-tunnel <slug>; open localhost:4700 Practically one active target per local port pair.

Level 1 is cheap and direct. It is the right default for gates, tests, and commands that do not need an app browser. Level 2 is heavier because privileged app boxes run an inner Docker daemon and the Osprey stack. It is still parallelizable, but bounded by the Studio’s CPU and RAM. Level 3 is human-interactive: the tunnel forwards local :4700 to the app gateway and local :4710 to Keycloak because the login redirect needs both origins.

Browser tunnel mode
just crabbox list
just crabbox app-tunnel <slug>
# open http://localhost:4700
# Keycloak is forwarded at http://localhost:4710

Because the tunnel binds fixed local ports, treat Level 3 as a single selected box at a time on a laptop. You can stop one tunnel and open another, or choose different local ports by changing the helper, but the default operator model is one interactive box per 4700/4710 pair.

Warning

If local just dev is already using 4700 or 4710, close it before opening the tunnel. The forwarded ports must match the app and Keycloak redirect origins.

Lifecycle is part of the contract

A remote dev box that never goes away is just a shared server with extra steps. Crabbox avoids that by giving boxes a lifecycle. crabbox warmup keeps a box available for reuse. Crabbox itself releases idle warm boxes. crabbox release tears down the selected lease. crabbox cleanup lets the provider remove expired containers. The Studio reaper is the safety net when a laptop dies mid-lease.

The provider labels containers with the lease, slug, creation time, and max_lifetime_secs. In privileged mode it also creates a lease-named /var/lib/docker volume so the inner Docker daemon gets a real lifecycle: removed on release, swept by cleanup, and pruned by the reaper when orphaned.

Cleanup habits
crabbox list
crabbox release --id <slug>
crabbox cleanup
ssh qs-mac-studio 'cat ~/.crabbox-studio/reap.log'

The most common scary-looking message is not a Studio failure at all: lease <id> claim changed; retry. The Crabbox source notes this as a benign optimistic-concurrency race in the local claim file, usually right after crabbox warmup. Re-run the command.

Other failures usually sort into three buckets: the laptop cannot reach qs-mac-studio; the provider cannot find Docker because non-interactive SSH has a minimal PATH; or an app command is running in a non-privileged test box. The first is Tailscale/SSH setup. The second is the docker: absolute path in crabbox.yaml. The third is a config choice: app boxes need privileged mode before they are created.

What to take forward

The Crabbox setup is not merely faster remote hardware. It is a way to give humans and agents branch-scoped, disposable, inspectable environments without turning the Mac Studio into a shared mutable workstation. The clean mental model is: use Level 1 for gates, Level 2 for full-stack proof and screenshots, Level 3 for one selected browser session. Keep slugs explicit once more than one box exists. Rebuild the runner when the toolchain changes. Let the reaper make forgotten boxes temporary.

  1. Crabbox on qs-mac-studio is direct-only: laptop CLI, SSH over Tailscale, Docker on the Studio, no coordinator.
  2. Each box gets a synced clone of the current branch, so branch choice happens before or during box creation and sync.
  3. Pure CLI Crabbox commands are the base layer and work well with multiple boxes when slugs are explicit.
  4. Osprey's just crabbox helpers add ensure, list, screenshot, and browser tunnel workflows.
  5. Claude Code should use crabbox-it when the agent needs an isolated branch box for gates, app boot, screenshots, or verification.
  6. The tunnel workflow is intentionally single-target by default because local ports 4700 and 4710 point at one selected box.

Discussion Prompts

  • Which gates should become the default Level 1 smoke set for every branch before a PR?
  • Should the repo carry two named Crabbox configs, one plain test config and one privileged app config, to avoid editing privileged by hand?
  • What screenshots should crabbox-it capture by default when validating an app-facing change?

References

  1. Crabbox - The CLI and provider model used by the Studio Docker external provider.
  2. Tailscale: What is Tailscale? - Background on the private network layer that makes the Studio reachable without exposing it publicly.
  3. Docker Docs: Running containers - The underlying container lifecycle Crabbox delegates to the Studio provider.