Appendices · reference

Events

12 min read · 2026-09-02 · vmlab 0.9

This chapter lists every event the daemons emit, grouped by area, with the fields each payload carries and when it fires. The events guide explains how to bind a handler script to one and what a handler may do; Event is the record a handler receives.

Shape and delivery

An event is one JSON object with four fields: event, the name; lab, the lab it belongs to, omitted for host-scoped events; data, the payload object, omitted when there is none; and ts, a UTC timestamp. A lab daemon writes every event it emits to three places: its tracing log, the lab's events.jsonl under the state directory (see Files and directories), and its broadcast stream, which the supervisor folds into a host-wide aggregate. A subscriber attaches at either level. vmlab logs reads the history file.

A handler script sees the machine an event concerns as event.vm, taken from the payload's vm key, else its container key, else empty. Events whose natural subject is machine set vm as well so a handler can look the machine up.

json
{"event":"vm.ready","lab":"demo","data":{"vm":"dc01"},"ts":"2026-09-02T10:14:03.512Z"}

Lab lifecycle

EventPayload fieldsWhen
lab.upvms: the names startedvmlab up finished, after provisioning and workspace start.
lab.downnonevmlab down finished; also on the way through destroy.
lab.daemon_crashednone; lab names the labEmitted by the supervisor when a lab daemon's socket is gone or unresponsive. The registry entry is marked failed.
host.disk_lowpath, free_percentEvery sixty seconds while free space under the lab's .vmlab/ is below the host config threshold. The supervisor emits the same event, with an empty lab, for the filesystem holding the template store.

Machines

VM events carry vm; container events carry container. reason on a stop is requested, guest_initiated or crashed; status is the emulator's exit status and exit_code the container's.

EventPayload fieldsWhen
vm.startingvmThe VM's start began, after any deferred template download.
vm.readyvmThe agent handshake completed (after any agent refresh up or vm start deferred to it), or an online snapshot finished loading, or the first-boot provision completed and was sealed. For a container in that last case the key is container.
vm.crashedvm, reason, statusThe emulator exited without being asked. Always followed by vm.stopped.
vm.stoppedvm, reason, statusThe emulator exited for any reason.
vm.suspendedvm, prevent_sleepThe guest suspended itself to RAM (ACPI S3); the VM now reports suspended. prevent_sleep = true means a wake is already on its way.
vm.wokenvm, cause, messageA suspended guest runs again. cause is prevent_sleep (the daemon woke it the moment it slept), start (vmlab vm start), stop (a graceful stop woke it first), restore (an online snapshot restore), or guest (its own wake source, or a reset).
vm.destroyedvmvmlab vm destroy removed the VM's clones, run directory and workspace ledger.
container.startingcontainerThe container's start began, after the image pull.
container.readycontainerThe micro-VM reported ready; port forwards are installed next.
container.crashedcontainer, reason, exit_codeThe container exited without being asked. Always followed by container.stopped.
container.stoppedcontainer, reason, exit_codeThe container exited for any reason.
container.unhealthycontainerThe container's healthcheck reported not healthy.
container.destroyedcontainervmlab container destroy removed everything the container materialised.
machine.agent_repairedvm, machine, agent_versionvmlab machine repair-agent pushed the host's agent; the machine is now diverged from its template.
machine.agent_updatedvm, machine, from, to, ok, errorvmlab up or vmlab vm start found the VM's agent stamp differing from the shipped agent and pushed it. With ok = true the machine is now diverged; with ok = false, error says why the push failed and the old agent is still in place. from is null when the agent never answered.
share.unmountablevm, share, reasonThe mount plan holds a share this guest cannot mount, or a mount step still failed when its retries ran out (share is set then). See Shared folders.

Snapshots

EventPayload fieldsWhen
snapshot.createdvm, name, onlineA snapshot was taken, of a VM or a container alike, after the workspace pre-flight flush.
snapshot.restoredvm, name, onlineA snapshot was restored, after the pin check and the syncer bracket.

Deleting a snapshot emits nothing.

Downloads

Deferred downloads emit under two prefixes: template.pull for a VM's disk image and container.pull for a container image. The subject key is vm or container to match, and one event is emitted per machine waiting on the download.

EventPayload fieldsWhen
template.pull.start, container.pull.startvm or container, reference, archA download began.
template.pull.progress, container.pull.progresssubject, reference, bytes_done, bytes_total, percent, and chunk, chunks for a template or layer, layers for an imageThe transport reported progress.
template.pull.done, container.pull.donesubject, referenceThe download completed.
template.pull.cancelled, container.pull.cancelledsubject, referenceThe vmlab pull waiting on it was interrupted. The job stays pending for a retry.
template.pull.error, container.pull.errorsubject, reference, errorThe download failed. The job stays pending.

Networking and shares

EventPayload fieldsWhen
smb.startedportThe bundled smbd came up on that host port for the lab's shares.
smb.failederrorThe share plan could not be computed, or smbd failed to start on every candidate port. A start failure carries smbd's own reason, which its log usually lacks.
forward.installedwhat, host_port, guestA forward is listening on that host port and leads to guest (ip:port). Emitted again each time it is re-installed: at readiness, at the end of up, and after a restart changes the lease.
forward.skippedwhat, reasonThe forward plan dropped a declared forward, or installing one failed at runtime.
forward.conflicthost_port, claimantsTwo declarations claim the same host port.
segment.peer.upsegment, peer, directionA cross-host trunk for a global segment came up. Host-scoped: emitted by the supervisor with no lab.
segment.peer.downsegment, peer, directionA trunk slot cleared, or the segment was torn down.

Playbooks

Every playbook.op.* event carries machine, playbook (the path), play and op_id, plus the fields below. See Playbooks.

EventPayload fieldsWhen
playbook.op.startbase, mode: apply or checkA run was admitted.
playbook.op.logbase, lineOne human-readable line from the run.
playbook.op.phasebase, phase: running or rebooting, attempt, maxThe run changed phase, so a reboot shows as one rather than a stall.
playbook.op.stepbase, cw: the engine's structured eventOne structured line from the guest engine.
playbook.op.donebase, exit_code, reboots, reportThe run finished without an infrastructure error, whatever its exit code.
playbook.op.errorbase, errorThe agent, the push or the exec failed.
playbook.appliedmachine, playbook, playAn apply exited 0.
playbook.failedmachine, playbook, play, mode, and exit_code when the run ranThe run exited non-zero, or failed before it could run.

Template builds and pushes

The supervisor emits these for vmlab template build and vmlab template push. Every payload carries template, arch and kind, which is build or push. See Templates and the store.

EventPayload fieldsWhen
template.op.startbase; version for a pushA build or push was admitted.
template.op.consolebaseThe build VM's console socket is available.
template.op.stepbase, event, dataThe build's synthetic lab emitted a playbook.* event, forwarded with its name and payload inside.
template.op.logbase, lineOne non-blank output line from a build or push.
template.op.donebase, versionThe build sealed that version, or the push completed.
template.op.cancelledbase; version for a pushThe vmlab template build or push driving it was interrupted, and the supervisor aborted the operation.
template.op.errorbase, error; version for a pushThe build or push failed.
template.builtarch, name, versionThe template landed in the store. Emitted on the build's own synthetic lab, so it appears in that lab's history file rather than in the supervisor stream.

Workspace syncer

Every workspace event carries machine. See Dev machines and the workspace syncer for what each condition means and how it is resolved.

EventPayload fieldsWhen
workspace.identityreasonAt up, the dev machine declares no default login, so the tree lands owned by the agent identity.
workspace.unavailableworkspace, reasonThe declared host workspace directory does not exist.
workspace.degradedreason, and path where one directory is meantA precondition could not be met: a non-elevated Windows login, a directory that refuses the case-sensitivity flag, or line-ending conversion that could not be turned off.
workspace.stoppedreasonThe host file watcher could not start; the syncer gave up.
workspace.deferredreason, retry_in_s; or reason, paths for a held .git lockA pass, the guest watch or the post-restore re-convergence failed and will be retried; or paths under a .git lock were left for the next pass.
workspace.rescanreasonThe guest watch overflowed or its channel dropped, so the guest tree is walked again and both directions block until it completes.
workspace.unwatcheddirectoryA host subtree could not be registered with the watcher.
workspace.skippedpath, reasonA walk skipped a path.
workspace.refusedpath, size, cap, reasonA file exceeds the size cap and was refused before transfer.
workspace.case_collisionpaths, reasonTwo paths differ only in case where the guest cannot tell them apart.
workspace.volumepath, paths, bytes, reasonA large burst of changes under one prefix. A warning; it never halts.
workspace.haltedreason, rules_changed, paths (each path and reason, capped), total, resolveConflicting changes on both sides halted the whole workspace until vmlab dev sync resolve.
workspace.left_standingpath, reasonOne side dropped a directory the other still has content in.
workspace.symlink_refusedpath, reasonThe guest would not take a symlink.
workspace.failedpath, reasonOne apply failed, or the halt marker could not be written into the guest.
workspace.syncedguest_placed, guest_removed, host_placed, host_removed, adoptedA pass moved something.
workspace.reseed_owedworkspace, reasonA restore failed after the ledger was marked rewound, so the re-seed still runs.
workspace.reconvergedplaced, removed, adopted, reasonThe post-restore re-seed completed.