boxer

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 ls

Either form prints one indented JSON document on stdout. These commands have a JSON form.

CommandPrints
statusAn object
lsAn array of sandbox rows
fork lsAn array of sandbox rows
forkAn array of names
down, stop, rmAn object or an array of { scope, removed }
gcAn array of decisions
backendsAn array of backends
integrationsAn array of integrations
briefAn object
tasksAn array of tasks
pack lsAn array of packs
logsAn array of events
doctorAn object
capsule inspectAn object
watchOne 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
scopeThe sandbox key and machine name
nameThe two-word form of scope. --scope accepts either
isolation, worktree, mount_atThe resolved scope and guest mount
existsWhether the sandbox exists. state is absent when it does not
image, image_reasonThe machine's image and machine, or when absent, the image boxer would boot and why
labelsThe backend labels, boxer.* keys. {} when absent
last_usedRFC 3339, or null
resourcesResources, when the sandbox exists
portsGuest port to host port. The only way to find an auto: forward
urlsGuest port to its [urls] name. Prefer it over ports. It does not change between restarts
eventsThe last five events for this scope, when the file telemetry sink is on
last_runThe 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
cpusAllocated
memory_mibAllocated
pidThe machine's process on the host
rss_mibResident memory
cpu_percentCPU use as the host sees it
disk_bytesThe machine's data directory
measured_alltrue 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 }
}
FieldPresent
scope, name, state, isolation, worktree, integration, image, created_at, last_usedAlways. created_at is Unix seconds. last_used may be null
harness, session, agentWhen the harness supplied them
backendAlways, so ls -A can mix backends
ports, urlsWhen it forwards ports, and for urls when [urls] names them
resourcesWith --resources
gitUnless --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
namesmolvm, docker, podman or container
configuredThis repository's backend
installed, binaryWhether the program is on PATH, and its path
versionThe version number it reports
reachable, sandboxesWhether it answered a listing, and how many boxer sandboxes it holds
capabilitiesboundary (kernel or namespace), host_mounts, allowlist, secret_env, mount_at_host_path, proves_ownership, packs, branch, egress, disk_usage, and backend
problemsKnown misconfigurations, in words
probeWith --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:

FieldValues
nameA harness install name, conductor, git-hook, portless, gh or npx
kindharness, orchestrator or tool
cliIts path, when on PATH
project, userinstalled, partial, no or n/a
skillyes or no
staleInstalled files written by another boxer release
fixThe 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
briefThe brief as prose
scope{ key, name, isolation, worktree, degraded, reason }
isolation, mount_at, mode, enforcementAs configured and resolved
intercept, passthroughThe lists
tasksTask name to command line
task_detailsName to { name, command, description, junit, timeout }, for tasks that declare more than a command. Omitted when none do
portsnetwork.ports as configured. Where they landed is in status
urlsWhether [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_pathThis binary
insideWhether doctor runs inside a guest
smolvm, smolvm_errorThe configured backend's version (smolvm's when the configuration did not resolve), or why it could not be read
backendThe configured backend's capabilities
git{ toplevel, linked }
config_filesEvery file read, lowest priority first
settings{ key, value, source } per setting. source is a file, a BOXER_* variable, harness.<name> or default
scopeAs in brief
image, image_reason, image_warningThe image boxer would use, why, and a warning about it
sandbox, sandbox_errorThis 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"
signalsPer harness, which signals boxer can receive and the isolation they allow
driftInstalled files that differ from this binary's copy
storage{ machines, pack_count, pack_bytes, free_bytes }
denied_hostsHosts the egress allowlist refused, when the sandbox exists on smolvm under allowlist
warningsConfigurations that look correct but do not work
errorWhy 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, scopeManifest version, when it was captured, and the sandbox it came from
image, packThe 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

CodeMeaning
0Success. For status, running
1Refused or failed
2Usage error
3For status, the sandbox exists and is stopped
4For status, url and open, no sandbox exists for this scope
nFor 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.

causeWhen
NO_REPOSITORYNot inside a git repository
WORKTREE_REQUIREDrequire_worktree = "require" in the main checkout
SCOPE_UNRESOLVEDon_missing_id = "fail" and the harness supplied no session or agent id
NO_SANDBOXNo 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_PACKpack use named a pack that was never saved
NO_IMAGENo image could be chosen
CONFIG_INVALIDA value is not valid: memory, build's Dockerfile, or an allow list
BACKEND_UNAVAILABLEThe backend is not answering: a stopped docker daemon, a missing smolvm
BUILD_FAILEDbuild's image could not be built, or its builder is not installed
AMBIGUOUS_NAMEA two-word name matches more than one sandbox; use the key
UNSUPPORTEDThe backend cannot do what the configuration asks, such as allowlist on docker
CREATE_FAILEDThe backend failed to create the sandbox, or another boxer is still creating it
START_FAILEDThe sandbox failed to start, or a start step could not be launched
PREP_FAILEDA prep.commands line exited non-zero on the host
SETUP_FAILEDA setup or image_setup line exited non-zero, or user cannot write the worktree
NOT_READYready did not pass within ready_timeout
NO_FREE_PORTAn auto: forward found no free host port
HARNESS_INSTALL_FAILEDInside mode could not install the harness in the guest
TRANSPORT_FAILEDboxer could not read its marker back from the guest
NOT_FORKABLEfork on a sandbox not prepared with fork --prepare
NO_RUN_RECORDcapsule new with no recorded run in this scope
DIRTY_WORKTREEcapsule replay on a worktree with uncommitted changes, without --allow-dirty
CAPSULE_DRIFTcapsule 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.

On this page