# Crabbox (https://tenki.cloud/docs/sandbox/crabbox)

> For the complete documentation index, see [llms.txt](https://tenki.cloud/llms.txt)

Keep a Tenki Sandbox warm and let Crabbox sync your working tree and run your tests on it, like CI that runs before you commit.

[Crabbox](https://crabbox.sh) is an open-source CLI that takes the working tree you have on your laptop, ships it to a remote Linux machine, runs a command there, and streams the output back. With Tenki as the provider, that machine is a sandbox session. Keep one warm and every `crabbox run` becomes a CI run for the code you have right now, uncommitted edits included, while your laptop stays free.

## The short version

```bash
tenki login
brew install openclaw/tap/crabbox
```

```yaml title=".crabbox.yaml"
provider: tenki
target: linux
tenki:
  diskGB: 20
```

```bash
crabbox warmup --slug ci
crabbox run --id ci --shell 'pnpm install --frozen-lockfile && pnpm test'
```

From here, every `crabbox run --id ci -- <command>` syncs only what changed and runs on the same box. The rest of this page explains each piece, adds watch mode, jobs, and environment variables, and shows how to tell your coding agents about it.

## How it fits together

Crabbox owns the loop. It syncs your files over SSH with rsync, runs your command, keeps run history, and collects test results. Tenki owns the machine, a full Linux VM that Crabbox creates, resumes, and terminates through the [`tenki` CLI](https://tenki.cloud/docs/sandbox/cli.md). The command is yours. Crabbox does not know how your project installs dependencies or runs tests, so whatever you type after `--` is what runs.

Crabbox reaches the sandbox through Tenki's SSH proxy with a per-session certificate. It never sees a guest IP, and you never manage SSH keys.

## Before you start

You need the `tenki` CLI signed in (see the [Quickstart](https://tenki.cloud/docs/sandbox/quickstart.md) if you have not installed it yet), Crabbox, and a Git checkout of your repository.

```bash
tenki login
brew install openclaw/tap/crabbox
```

Sign-in binds the CLI to a workspace, and Crabbox creates its sandboxes there using the CLI's saved credentials. There is no separate token to configure, and you should not pass Tenki API keys on the command line. To point Crabbox at a different workspace, run `tenki login` again. If you do not use Homebrew, [GitHub Releases](https://github.com/openclaw/crabbox/releases) ship archives for macOS, Linux, and Windows.

Git matters because Crabbox decides what to sync by asking Git what is tracked and what is not ignored.

## 1. Point the repository at Tenki

Create `.crabbox.yaml` at the repository root. This is the whole configuration for most projects:

```yaml title=".crabbox.yaml"
provider: tenki
target: linux
tenki:
  cpus: 4
  memoryMB: 8192
  diskGB: 20
sync:
  exclude:
    - node_modules
    - dist
    - .turbo
```

The `tenki` block sets the sandbox size. Without it you get Tenki's defaults of 2 vCPU, 4 GB of memory, and a 5 GiB root disk, and 5 GiB fills up fast once dependencies are installed, so set `diskGB` to 20 for anything with a `node_modules` or a Go module cache. `sync.exclude` keeps generated output from being shipped up; Git-ignored files are already excluded, so this list only matters for build output you have not ignored.

Check that Crabbox can see the provider and your login:

```bash
crabbox doctor
```

The last line should read `provider provider=tenki ... auth=ready`. If it says the CLI is not logged in, run `tenki login` and try again.

## 2. Run a command once

```bash
crabbox run --shell 'pnpm install --frozen-lockfile && pnpm test'
```

Crabbox creates a sandbox, waits for SSH, syncs the repository to `/home/tenki/crabbox/<lease-id>/<repo-name>`, runs the command in that directory, streams the output, and terminates the sandbox when the command finishes. The exit code is the remote command's exit code, so this works in scripts and Git hooks as is.

`--shell` hands the string to the remote shell, which is what makes `&&` work. Without it, everything after `--` is one program and its arguments:

```bash
crabbox run -- pnpm test
```

Two things worth knowing about the sync:

* It sends tracked files plus untracked files that Git would not ignore. Your uncommitted edits go up; your `.env` does not, as long as Git ignores it and has never tracked it. A file that was committed before it was added to `.gitignore` is still tracked, and still syncs.
* The sandbox starts empty, which is why the install is part of the command. A one-shot run pays for it every time; the next step keeps a box around so it happens once.

## 3. Keep a box warm

This is the always-on part. Lease a sandbox once and give it a name:

```bash
crabbox warmup --slug ci
```

Warmup prints the lease ID and the slug, and the sandbox is ready in about ten seconds. Crabbox marks it `sticky`, so Tenki will not pause it for being idle or stop it at a maximum duration. It runs until you stop it.

Now aim every run at that slug:

```bash
crabbox run --id ci -- pnpm install --frozen-lockfile
crabbox run --id ci -- pnpm test
```

The first run installs dependencies into the sandbox. After that, each run only syncs the files that changed since the last one, and `node_modules` stays put on the box.

Other things you can do with the warm box:

```bash
# Rerun the tests every time a synced file changes
crabbox watch --id ci -- pnpm test

# Open a shell in the sandbox's workspace
crabbox ssh --id ci

# See whether it is up and how long it has been idle
crabbox status --id ci

# Every run Crabbox has recorded, newest first
crabbox history
```

`watch` debounces filesystem events, ignores anything that would not sync anyway, and never overlaps two runs. A failing test run is a normal iteration; the loop keeps watching. It exits on its own after a quiet period, which defaults to the lease idle timeout of 30 minutes, and passing `--id` means exiting the loop leaves the sandbox running.

### Pausing overnight

A sticky sandbox bills while it runs. If you want to keep the installed state but stop paying overnight, pause the session from the Tenki side:

```bash
crabbox list --provider tenki      # shows the Tenki session ID next to the slug
tenki sandbox pause --session <session-id>
```

Pausing stops the compute meter and keeps the disk and memory; the saved state counts toward your storage quota. The next `crabbox run --id ci` notices the session is paused, resumes it, and carries on. `/tmp` is cleared across a pause. Everything under `/home/tenki`, including the synced workspace and its dependencies, survives.

## Environment variables

Only variables you allow are forwarded. Out of the box that is `CI` and `NODE_OPTIONS`, plus Crabbox's own `CRABBOX_LEASE_ID`, `CRABBOX_RUN_ID`, and `CRABBOX_SLUG`. Everything else in your shell stays on your laptop, so a test that reads `DATABASE_URL` passes locally and fails on the box until you allow it.

For one run, name the variable on the command line:

```bash
DATABASE_URL=postgres://localhost/app crabbox run --id ci --allow-env DATABASE_URL -- pnpm test
```

To allow it for every run, list it in `.crabbox.yaml`. This list replaces the built-in one, so repeat `CI` and `NODE_OPTIONS` if you still want them:

```yaml title=".crabbox.yaml"
env:
  allow:
    - CI
    - NODE_OPTIONS
    - DATABASE_URL
```

Values can come from a file instead of your shell. Only names on the allowlist are read from it; anything else in the file is ignored, and the file itself stays local as long as Git ignores it:

```bash
crabbox run --id ci --env-from-profile .env.ci -- pnpm test
```

Keep tokens off the allowlist. A forwarded value lands in the process environment of every command on a box that stays up all day. Fetch secrets inside the command from a secret store, or bake them into a [template](https://tenki.cloud/docs/sandbox/templates.md) if they have to be on the machine.

## 4. Turn it into a job

Once the command settles, write it down so nobody has to remember the flags. Jobs live in the same `.crabbox.yaml`:

```yaml title=".crabbox.yaml"
provider: tenki
target: linux
tenki:
  cpus: 4
  memoryMB: 8192
  diskGB: 20
jobs:
  test:
    shell: true
    command: pnpm install --frozen-lockfile && pnpm test
    stop: auto
```

```bash
crabbox job run test            # new sandbox, stopped when the job ends
crabbox job run --id ci test    # runs on the warm box and leaves it running
crabbox job run --dry-run test  # prints the commands it would run
```

`stop: auto` is the policy that makes both lines above do the right thing: a sandbox the job created is terminated afterwards, and a sandbox you passed with `--id` is left alone. If your test runner writes JUnit XML, add `junit: [junit.xml]` to the job, or `--junit junit.xml` on a plain run, and a failed run lists the failing tests instead of making you scroll the log.

`crabbox init --detect` can write a starting config for you. It infers a test command from `package.json`, `go.mod`, `Cargo.toml`, or a `Makefile` and writes it as a job named `detected`. The file it writes assumes a cloud VM provider, so edit it before the first run: add `provider: tenki` and `target: linux` at the top, and delete the `actions` block along with the `.github/workflows/crabbox.yml` it wrote next to it. With that block in place every run tries to hydrate the box from that workflow first and fails with `local Actions hydration exited before writing marker` before your command starts. The `profile`, `class`, `capacity`, `ssh`, and `cache` blocks are for other providers; Tenki ignores them, so delete them or leave them. The `sync`, `env`, and `jobs` blocks are worth keeping.

### Optional: gate pushes on it

Crabbox exits with the remote command's exit code, so a failing run blocks the push if you call it from a pre-push hook:

```bash title=".git/hooks/pre-push"
#!/bin/sh
exec crabbox job run --id ci test
```

Make it executable with `chmod +x .git/hooks/pre-push`. With `stop: auto` the warm box stays up afterwards, and `git push --no-verify` skips the hook when you need to push anyway.

## 5. Tell your agents about it

A coding agent working in the repository needs to know that checks run on the warm box, not on the machine it is sitting on. A short section in `AGENTS.md` or `CLAUDE.md` covers it:

```markdown title="AGENTS.md"
## Running checks

Checks run on a Tenki sandbox through Crabbox, not locally.

- Warm box: `crabbox warmup --slug ci`. Skip it if `crabbox status --id ci` prints `state=ready`.
- Run a command there: `crabbox run --id ci -- <command>`. The full suite is `crabbox job run --id ci test`.
- Only tracked and non-ignored files sync. Ignored inputs the checks need, such as generated code, go up once with `crabbox cp --id ci <local-path> SANDBOX:<remote-path>`.
- Leave the box running. Do not `crabbox stop ci`.
```

Adjust the slug, the job name, and the list of ignored inputs to match your repository. `crabbox init` also writes a generic skill file at `.agents/skills/crabbox/SKILL.md` that teaches an agent the warm, run, inspect, stop workflow for any provider. Keep that file and add the section above for the repository-specific parts.

## Start from a snapshot with a warm package store

A fresh box has to install dependencies, and most of that time is spent downloading. Package managers keep a store outside the project, pnpm under `~/.local/share/pnpm` and npm under `~/.npm`, and a [snapshot](https://tenki.cloud/docs/sandbox/snapshots.md) of a box that has already installed once carries that store along. Run the install on a warm box, snapshot it, and tell Crabbox to start new sandboxes from the snapshot:

```bash
crabbox list --provider tenki
tenki sandbox snapshot create --session <session-id> --name deps-ready
```

```yaml title=".crabbox.yaml"
tenki:
  snapshot: <snapshot-id>
```

The same setting is available per run as `--tenki-snapshot <id>`. If you build environments as [templates](https://tenki.cloud/docs/sandbox/templates.md) rather than snapshots, use `tenki.image` with the published image ref instead. The two are mutually exclusive.

This does not skip the install step. Crabbox names the workspace after the lease ID, so a sandbox restored from the snapshot gets a fresh, full sync into a new directory and the `node_modules` inside the old workspace is never used. What the snapshot saves is everything outside the workspace: the toolchain, anything you installed system-wide, and the package store, which turns the install into a link step instead of a download.

## Sizing per run

The `tenki` block in config sets the default size. The size flags only apply when Crabbox creates a sandbox, so override them on a one-shot run when one command needs more:

```bash
crabbox run --tenki-cpus 8 --tenki-memory-mb 16384 -- pnpm test:integration
```

A warm box keeps the size it was created with, and passing the flags together with `--id` has no effect. For a bigger warm box, pass them to `warmup`:

```bash
crabbox warmup --slug ci-big --tenki-cpus 8 --tenki-memory-mb 16384 --tenki-disk-gb 40
```

## Cost and cleanup

* A one-shot `crabbox run` terminates its sandbox when the command exits, success or failure.
* A warm box from `warmup` runs until you stop it. `crabbox stop ci` terminates the Tenki session and forgets the lease.
* `crabbox list --provider tenki` shows every sandbox Crabbox created from this machine. In the Tenki dashboard and in `tenki sandbox list`, the same sessions carry the tags `crabbox` and `crabbox-provider-tenki`.
* Crabbox only terminates sessions it created. If you delete a session with `tenki sandbox terminate` directly, the next `crabbox stop` for that slug cleans up the local record.

## Troubleshooting

**`crabbox doctor` says the Tenki CLI is not logged in.** Run `tenki login`, then `tenki status`. Crabbox refuses to create anything until the CLI has a valid key.

**The sandbox ended up in the wrong workspace.** Crabbox inherits the workspace from the Tenki CLI's saved key. Run `tenki login` and pick the workspace you want, then `crabbox stop` the stray slug.

**Every run reinstalls dependencies.** You are running without `--id`, so each run gets a fresh sandbox. Warm one up with `crabbox warmup --slug ci` and pass `--id ci`.

**Runs fail with "no space left on device".** The default root disk is 5 GiB. Set `tenki.diskGB: 20` and warm up a new box; the disk size cannot change on an existing sandbox.

## What's next

* [Sessions](https://tenki.cloud/docs/sandbox/sessions.md) for everything the sandbox itself can do, including port exposure if your tests start a server you want to look at.
* [Snapshots](https://tenki.cloud/docs/sandbox/snapshots.md) and [Templates](https://tenki.cloud/docs/sandbox/templates.md) for pre-baked environments.
* [Crabbox documentation](https://crabbox.sh): the [Tenki provider page](https://crabbox.sh/providers/tenki.html), [jobs](https://crabbox.sh/features/jobs.html), and the [command reference](https://crabbox.sh/commands/index.html).