JSON output
The JSON each boxer command prints, its exit codes, and its error shape.
Add --json, or set BOXER_OUTPUT=json, to get a boxer command's output as JSON.
boxer status --json
BOXER_OUTPUT=json boxer lsEither form prints one indented JSON document on stdout. These commands have a JSON form.
| Command | Prints |
|---|---|
status | An object |
ls | An array of sandbox rows |
fork ls | An array of sandbox rows |
fork | An array of names |
down, stop, rm | An object or an array of { scope, removed } |
gc | An array of decisions |
backends | An array of backends |
integrations | An array of integrations |
brief | An object |
tasks | An array of tasks |
pack ls | An array of packs |
logs | An array of events |
doctor | An object |
capsule inspect | An object |
watch | One object per line |
up and run accept --json for the refusal only. Fields are snake_case. Fields are added, never renamed or removed. Ignore fields you do not know.
boxer status --json
{
"scope": "sb-7e1852e4a3c3",
"name": "swift-crab",
"isolation": "worktree",
"worktree": "/Users/me/src/demo",
"mount_at": "/workspace",
"exists": true,
"state": "running",
"image": "node:24-bookworm-slim",
"image_reason": "machine",
"labels": { "boxer.root": "/Users/me/src/demo" },
"last_used": "2026-09-20T14:31:09Z",
"resources": { "cpus": 4, "memory_mib": 4096, "pid": 51234, "rss_mib": 612, "disk_bytes": 1073741824, "measured_all": true },
"ports": { "3000": "61234" },
"urls": { "3000": "https://fix-ui.demo.localhost:1355" },
"last_run": {
"time": "2026-09-20T14:31:09Z",
"scope": "sb-7e1852e4a3c3",
"task": "test",
"command": "npm test",
"dir": ".",
"exit": 1,
"duration_ms": 8421,
"git_head": "9f3c1a…",
"dirty": true,
"tests": { "tests": 131, "failures": 3, "errors": 0, "skipped": 2, "failed": ["cart/CheckoutTest.applies_discount"] }
}
}Exit 0 running, 3 stopped, 4 absent, 1 refused.
| Field | |
|---|---|
scope | The sandbox key and machine name |
name | The two-word form of scope. --scope accepts either |
isolation, worktree, mount_at | The resolved scope and guest mount |
exists | Whether the sandbox exists. state is absent when it does not |
image, image_reason | The machine's image and machine, or when absent, the image boxer would boot and why |
labels | The backend labels, boxer.* keys. {} when absent |
last_used | RFC 3339, or null |
resources | Resources, when the sandbox exists |
ports | Guest port to host port. The only way to find an auto: forward |
urls | Guest port to its [urls] name. Prefer it over ports. It does not change between restarts |
events | The last five events for this scope, when the file telemetry sink is on |
last_run | The run record, when a command has run |
Run record
time, scope, command, dir (worktree-relative), exit, duration_ms and dirty are always
present. task, image, pack, git_head, config_files, stdout_tail, stderr_tail (up to
8 KiB each) and tests appear when set. tests has tests, failures, errors, skipped and
failed, and is filled when the run found JUnit reports.
Resources
| Field | |
|---|---|
cpus | Allocated |
memory_mib | Allocated |
pid | The machine's process on the host |
rss_mib | Resident memory |
cpu_percent | CPU use as the host sees it |
disk_bytes | The machine's data directory |
measured_all | true when every value was read |
Every field but cpus and memory_mib is omitted when zero or unread, so a missing
measured_all means something could not be measured. A failed measurement never fails the
command.
boxer ls --json
An array, one row per sandbox:
{
"scope": "sb-7e1852e4a3c3",
"name": "swift-crab",
"state": "running",
"isolation": "worktree",
"worktree": "/Users/me/src/demo",
"integration": "outside",
"image": "node:24-bookworm-slim",
"created_at": 1758377469,
"last_used": "2026-09-20T14:31:09Z",
"harness": "claude-code",
"session": "01J9X…",
"backend": "smolvm",
"ports": { "3000": "61234" },
"git": { "exists": true, "branch": "fix-ui", "dirty": true, "changed": 3, "ahead": 1, "behind": 0 }
}| Field | Present |
|---|---|
scope, name, state, isolation, worktree, integration, image, created_at, last_used | Always. created_at is Unix seconds. last_used may be null |
harness, session, agent | When the harness supplied them |
backend | Always, so ls -A can mix backends |
ports, urls | When it forwards ports, and for urls when [urls] names them |
resources | With --resources |
git | Unless --no-git |
git has exists, dirty, changed (the number of changed paths), ahead and behind, plus
branch on a branch, detached: true on a detached HEAD, and with --pr a
pr: { number, state, url } from gh. exists: false means the worktree is gone and the sandbox
is waiting for gc or rm --gone.
boxer fork ls --json and boxer fork --json
fork ls prints an array of this scope's children in the ls row shape without backend,
ports, git and resources. fork prints an array of the new children's names, [] when the
scope already had that many.
boxer down, stop and rm --json
{ "scope": "sb-7e1852e4a3c3", "removed": true }down prints one object. removed is false when there was no sandbox. down --all,
down --scope, stop and rm print an array, one per sandbox acted on, and [] when nothing
matched; down --scope with a name that matches no sandbox exits 1 with an error
instead. stop rows have removed: false. A sandbox that failed has error. An rm --volumes
row can have both removed: true and error when the sandbox went but its volumes did not.
boxer gc --json
An array of decisions. A sandbox row is the ls row without backend, ports and git, plus
reason, deleted and, on failure, error. A pack row has pack (its path), bytes, reason,
deleted and error, with the sandbox fields empty. A volume row has volumes (the scope whose
volumes they are), reason, deleted and error, with the sandbox fields empty. A sandbox that
idle_action = "stop" stopped has stopped: true and
deleted: false. Under --dry-run, deleted and stopped are false.
boxer backends --json
An array, one object per backend:
| Field | |
|---|---|
name | smolvm, docker, podman or container |
configured | This repository's backend |
installed, binary | Whether the program is on PATH, and its path |
version | The version number it reports |
reachable, sandboxes | Whether it answered a listing, and how many boxer sandboxes it holds |
capabilities | boundary (kernel or namespace), host_mounts, allowlist, secret_env, mount_at_host_path, proves_ownership, packs, branch, egress, disk_usage, and backend |
problems | Known misconfigurations, in words |
probe | With --probe, { ok, seconds, error } |
Exit 1 when the configured backend is missing or not answering, or a probe failed.
boxer integrations --json
An array, one object per integration:
| Field | Values |
|---|---|
name | A harness install name, conductor, git-hook, portless, gh or npx |
kind | harness, orchestrator or tool |
cli | Its path, when on PATH |
project, user | installed, partial, no or n/a |
skill | yes or no |
stale | Installed files written by another boxer release |
fix | The command that wires it in, for a harness on PATH installed nowhere |
installed is decided by asking boxer's own installer which files it would write, so this and
boxer install agree. Orchestrator rows appear only inside a repository.
boxer brief --json
| Field | |
|---|---|
brief | The brief as prose |
scope | { key, name, isolation, worktree, degraded, reason } |
isolation, mount_at, mode, enforcement | As configured and resolved |
intercept, passthrough | The lists |
tasks | Task name to command line |
task_details | Name to { name, command, description, junit, timeout }, for tasks that declare more than a command. Omitted when none do |
ports | network.ports as configured. Where they landed is in status |
urls | Whether [urls] is on |
boxer tasks --json
An array of { name, command, description, junit, timeout }, sorted by name. Empty fields are
omitted.
boxer pack ls --json
An array of { name, path, bytes, saved_at }, newest first.
boxer logs --json
An array of events in the event shape, oldest first.
boxer doctor --json
| Field | |
|---|---|
version, boxer_path | This binary |
inside | Whether doctor runs inside a guest |
smolvm, smolvm_error | The configured backend's version (smolvm's when the configuration did not resolve), or why it could not be read |
backend | The configured backend's capabilities |
git | { toplevel, linked } |
config_files | Every file read, lowest priority first |
settings | { key, value, source } per setting. source is a file, a BOXER_* variable, harness.<name> or default |
scope | As in brief |
image, image_reason, image_warning | The image boxer would use, why, and a warning about it |
sandbox, sandbox_error | This scope's sandbox in the fork ls row shape, or null, and why it could not be read |
environment | { key, pack, cached }. The environment pack this scope would use, and whether it exists |
shims | { on_path, missing } under enforcement = "shim" or "both" |
signals | Per harness, which signals boxer can receive and the isolation they allow |
drift | Installed files that differ from this binary's copy |
storage | { machines, pack_count, pack_bytes, free_bytes } |
denied_hosts | Hosts the egress allowlist refused, when the sandbox exists on smolvm under allowlist |
warnings | Configurations that look correct but do not work |
error | Why the configuration could not be resolved. Exit 1 when set |
Each denied_hosts entry is a host to add to allow_hosts. doctor reports every refusal the
machine remembers. A failed boxer run reports only the refusals during that command.
boxer capsule inspect --json
The manifest, with the same field names as capsule.toml. Empty optional fields are left out.
{
"version": 1, "created": "2026-09-20T14:35:02Z", "scope": "sb-7e1852e4a3c3",
"image": "node:24-bookworm-slim",
"run": { "task": "test", "command": "npm test", "dir": "." },
"git": { "head": "9f3c1a…", "dirty": true, "patch": "capsule.patch" },
"config": { "setup": ["npm ci"], "network_mode": "allowlist" },
"expect": { "exit": 1, "failing_tests": ["cart/CheckoutTest.applies_discount"] }
}| Field | |
|---|---|
version, created, scope | Manifest version, when it was captured, and the sandbox it came from |
image, pack | The image, and the environment pack when one was in use |
run | { task, command, dir }: the command, and dir relative to the worktree |
git | { head, dirty, patch }: the commit, whether the worktree had changes, and the patch file for them |
config | { image, image_setup, setup, env, mounts, network_mode, allow_hosts }: the settings that shape the guest |
expect | { exit, failing_tests }: what a replay must reproduce |
Exit codes
| Code | Meaning |
|---|---|
0 | Success. For status, running |
1 | Refused or failed |
2 | Usage error |
3 | For status, the sandbox exists and is stopped |
4 | For status, url and open, no sandbox exists for this scope |
| n | For run, the command's own exit code |
The CLI reference lists the command-specific cases.
Errors
A refusal prints boxer: <reason> on stderr with scope, worktree, cause and fix lines. With
--json, up, run, down, status, brief and tasks also print it on stdout when the scope
cannot be resolved, as do run --task with an unknown task, run --scope and status --scope
with an unknown sandbox, and a failed down --scope. Exit 1.
{
"error": {
"reason": "not inside a git repository",
"cause": "NO_REPOSITORY",
"fix": "cd into a git worktree, or `git init`",
"scope": { "key": "", "name": "", "isolation": "", "worktree": "", "degraded": false }
}
}cause is a stable identifier. reason and fix are for people. An error that is not a boxer
refusal, such as an invalid boxer.toml, has only reason. The same refusal is the text of a
failed MCP tool call and a *boxer.Error in
Go.
cause | When |
|---|---|
NO_REPOSITORY | Not inside a git repository |
WORKTREE_REQUIRED | require_worktree = "require" in the main checkout |
SCOPE_UNRESOLVED | on_missing_id = "fail" and the harness supplied no session or agent id |
NO_SANDBOX | No sandbox exists and create_on does not allow creating one here, or pack save has none to save |
NO_SUCH_SCOPE | --scope named a sandbox that does not exist |
NO_SUCH_TASK | --task named a task boxer.toml does not declare |
NO_SUCH_PACK | pack use named a pack that was never saved |
NO_IMAGE | No image could be chosen |
CONFIG_INVALID | A value is not valid: memory, build's Dockerfile, or an allow list |
BACKEND_UNAVAILABLE | The backend is not answering: a stopped docker daemon, a missing smolvm |
BUILD_FAILED | build's image could not be built, or its builder is not installed |
AMBIGUOUS_NAME | A two-word name matches more than one sandbox; use the key |
UNSUPPORTED | The backend cannot do what the configuration asks, such as allowlist on docker |
CREATE_FAILED | The backend failed to create the sandbox, or another boxer is still creating it |
START_FAILED | The sandbox failed to start, or a start step could not be launched |
PREP_FAILED | A prep.commands line exited non-zero on the host |
SETUP_FAILED | A setup or image_setup line exited non-zero, or user cannot write the worktree |
NOT_READY | ready did not pass within ready_timeout |
NO_FREE_PORT | An auto: forward found no free host port |
HARNESS_INSTALL_FAILED | Inside mode could not install the harness in the guest |
TRANSPORT_FAILED | boxer could not read its marker back from the guest |
NOT_FORKABLE | fork on a sandbox not prepared with fork --prepare |
NO_RUN_RECORD | capsule new with no recorded run in this scope |
DIRTY_WORKTREE | capsule replay on a worktree with uncommitted changes, without --allow-dirty |
CAPSULE_DRIFT | capsule replay where HEAD differs from the capsule's, without --allow-drift |
Watching
boxer watch --json [--interval 1s]One JSON object per line. Two kinds of line share the stream.
A state change, found by comparing successive listings:
{ "time": "2026-09-20T14:31:09Z", "change": "created", "sandbox": { "scope": "sb-7e1852e4a3c3", "state": "running" } }change is present for what existed when the watch started, created for a new sandbox, gone
for a deleted one, and otherwise the backend's new state, such as running or stopped.
sandbox is the fork ls row. In a gone line,
only scope and state: "gone" are filled.
An event, when the file telemetry sink is on:
{ "time": "2026-09-20T14:31:10Z", "change": "event", "event": { "time": "2026-09-20T14:31:10.236Z", "event": "run", "outcome": "ok" } }event is the event shape. Only events written after the watch
starts appear. Tell the two kinds apart by sandbox or event, and ignore a line shape you do not
recognise.