Events
The JSON event stream boxer writes when telemetry is on, its schema, and how to read it.
With telemetry on, boxer writes one JSON event per line for each thing it decides or does.
[telemetry]
enabled = true
sink = "file" # none | file | stderr | otel
path = "" # default $XDG_STATE_HOME/boxer/events.jsonl
record_commands = false
endpoint = "" # otel onlyThe stream is off by default. With no [telemetry] table and no BOXER_TRACE, boxer writes
nothing. The keys are in the configuration reference.
BOXER_TRACE=<path> turns on the file sink at that path, whatever the configuration says. enabled = true with no sink uses the file sink. The OpenTelemetry sink exists only in a binary built with
-tags otel.
Shape
{
"time": "2026-09-20T14:31:09.236Z",
"event": "provision",
"scope": "sb-7e1852e4a3c3",
"harness": "claude-code",
"duration_ms": 623.4,
"outcome": "ok",
"payload": { "created": true, "image": "node:24-bookworm-slim", "image_reason": "detected package-lock.json" }
}| Field | |
|---|---|
time | RFC 3339 with nanoseconds, UTC |
event | One of the names below |
scope | The sandbox key. Absent for hook, rewrite, deny, mcp and gc |
harness | The harness, when one was named |
duration_ms | How long it took, when measured |
outcome | ok, error, denied or skipped |
payload | Event-specific fields |
scope, harness, duration_ms, outcome and payload are omitted when empty. Treat a missing
field as absent, and ignore event names and payload keys you do not recognise. Names and fields are
added, never repurposed.
event | When | payload |
|---|---|---|
resolve | A scope was resolved | isolation, degraded, warnings (a count) |
provision | A sandbox was created or started, prep ran, or a fork was prepared or branched | created, image and image_reason, or kind (prep, fork-prepare, fork) and count |
pack | A pack was written, skipped for disk space, or failed | kind (env, harness, image, named), image or name, path, free_bytes when skipped |
run | A command ran in the guest | exit, command |
rewrite | A hook replaced a command with its sandboxed form | tool, command |
deny | A hook refused a command | tool, reason |
hook | A hook was invoked | event (the harness's hook name), purpose |
mcp | An MCP tool was called | tool, command |
gc | A sweep ran | machines, packs, dry_run |
error | A refusal | cause, reason |
Redaction
Unless record_commands = true, the payload keys command, argv, env and setup are replaced
by their length, as command_len and so on. Values of env_passthrough and secrets never enter an
event. The separate last-run record is covered in the
security model.
Reading it back
boxer logs -n 50 # the last 50 events
boxer logs --json # the raw events, as an array
boxer logs --scope NAME # one sandbox
boxer watch --json # new events, interleaved with state changesThe file sink also holds plain-text hook trace lines. boxer logs skips anything
that is not an event.