Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

21. Environment, files and exit codes

Everything Zygo reads from its environment, everything it writes to disk, and every way it can end. Written from the code; if you script around Zygo, this is the chapter to keep open.

Environment variables Zygo reads

VariableRead byMeaningDefault
ZYGO_DATA_HOMECLI, supervisorThe data folder (same as --data-root).$XDG_DATA_HOME/zygo, else ~/.local/share/zygo
ZYGO_RUNTIME_DIRCLI, supervisorThe runtime folder: sockets and pid files.$XDG_RUNTIME_DIR/zygo, else /tmp/zygo-<uid>
ZYGO_LOGCLILog filter, e.g. debug or zygo=trace. Overrides -v. Logs go to stderr.warn
NO_COLORCLIAny value turns colour off. Colour is also off when output is not a terminal.
ZYGO_API_TOKENzygo api, SDKsThe bootstrap bearer token. Never a flag, because flags show in ps. It is removed from the environment of sandboxes the API starts.
ZYGO_API_URLSDKsWhere the API is: unix:///path, http://host:port or host:port.http://127.0.0.1:7700
ZYGO_SECRETS_KEYsupervisorThe 32-byte key of the secret store, as 64 hex characters or base64.none: secret routes refused
ZYGO_SECRETS_KEY_FILEsupervisorA file holding that key. Setting both is an error.
ZYGO_ALLOW_SHARED_UIDsupervisor1 lets tenants be registered on a host whose user has no subordinate uid range, where every tenant’s sandbox runs as the same host uid. Without it, POST /tenants is refused there (chapter 23).unset: refused
ZYGO_BYTECODEimage store0 turns off the Python bytecode layer.on
OTEL_EXPORTER_OTLP_ENDPOINTzygo apiSame as --otlp-endpoint.
OTEL_EXPORTER_OTLP_HEADERSzygo apiExtra headers for OTLP, as key=value,key=value.
DOCKER_CONFIGpull, loginWhere Docker’s config.json is, for registry credentials.~/.docker
ZYGO_KRUN_CONSOLEvm backendWrite the guest’s console to this file, for debugging.
ZYGO_IN_SCOPECLISet by Zygo itself when it re-runs inside a systemd scope, so it does not do it twice. Do not set it by hand.

Only on a Mac

VariableMeaning
ZYGO_LINUX_BINThe Linux build of Zygo to copy into the VM, before the ones Zygo looks for itself.
ZYGO_LIMA_TEMPLATEThe Lima template to create the VM from.
LIMA_HOMEWhere Lima keeps its VMs (~/.lima).

Every ZYGO_* variable in your shell is passed into the VM; so are the variables named by --secret and by secrets = [...] in the spec. Nothing else from your shell crosses.

Environment a sandbox receives

VariableWho sets itMeaning
ZYGO_FUNCTIONZygoThe function’s name. (ZYGO_TENANT holds the same value; the name is historical.)
HOMEZygo/tmp, unless the image or env sets one — so programs that write to ~ work on a read-only root.
ZYGO_REQUEST_IDthe agent, per requestThe request’s id.
ZYGO_DEADLINE_MSthe agent, per requestThe request’s time budget in milliseconds (its timeout); 0 means none.
TMPDIR, TMP, TEMPthe agent, per requestThe request’s own temporary folder: the workspace if one was sent, otherwise /work/tmp-<random>. Removed when the request ends.
ZYGO_AGENT_TMP_PARENTyou, for an agent’s testsWhere the reference agents make those folders instead of /work. Nothing in Zygo sets it.
ZYGO_WORKSPACEthe agent, per requestThe request’s workspace folder, when one was sent; the handler starts in it.
ZYGO_CHILD_SECCOMPZygo, under seccomp = "strict"The filter each forked child installs after GO, as base64 of a raw seccomp program. For the agent; a handler never needs it (chapter 24).
ZYGO_CHILD_SECCOMP_HELPERyou, for the Node agentThe path of the small shared object that lets a Node worker install that filter. Without it, the agent looks for zygo_child_seccomp.so beside itself, then falls back to Node’s permission model (chapter 18).
your envyouEverything in env / --env.

Secrets are not environment variables: they are files in /run/secrets/.

Files Zygo writes

~/.local/share/zygo/                    the data folder (ZYGO_DATA_HOME)
├── images/
│   ├── blobs/sha256/…                  compressed layers, as downloaded
│   ├── layers/<digest>/                unpacked layers, shared by every sandbox
│   └── index.json                      image name → manifest
├── cache/
│   ├── venvs/<key>/                    one venv per (image, requirements)
│   ├── flat/<digest>/                  flattened roots, where overlayfs can't be used
│   ├── system/                         records of derived apt layers
│   └── bytecode-failed/                bytecode builds that failed, not retried
├── scripts/  blobs/  deps/             what the API stores: scripts, tars, dependency sets
├── tenants/                            one JSON file per tenant
├── secrets/<tenant>.json               encrypted (ChaCha20-Poly1305); names only readable
├── tokens.json                         token hashes, never secrets        (mode 0600)
├── auth.json                           `zygo login` credentials           (mode 0600)
├── agents/                             the Python and Node agents, kept up to date
├── backends/                           gvisor/runsc · krun/Image (the vm guest kernel)
└── tmp/                                locks, and sandbox roots while they exist

$XDG_RUNTIME_DIR/zygo/                  the runtime folder (ZYGO_RUNTIME_DIR), mode 0700
├── supervisor.sock                     how the CLI talks to the supervisor (mode 0600)
├── supervisor.pid
├── host-report.json                    a short-lived cache of the host checks
└── tenants/<name>/agent.sock …         one socket per warm sandbox

Beside your project, zygo up writes zygo.lock, which you should commit. zygo doctor --fix may write ~/.config/systemd/user/user@.service.d/delegate.conf, /etc/sysctl.d/60-zygo-userns.conf, /etc/apparmor.d/zygo (the profile that lets this binary use user namespaces) and /etc/systemd/system/zygo-cgroup-favordynmods.service, and prints each one before it does. Putting pasta’s profile in complain mode runs aa-complain, which edits /etc/apparmor.d/usr.bin.passt or leaves a marker in /etc/apparmor.d/force-complain/. On a Mac, the VM lives in ~/.lima/zygo/. There is no Zygo config file: the spec and the lock are the only configuration.

Exit codes

  0 ─────── success
  1 ─────── a Zygo error, a failed check, a failed function in `up`, a failed batch line
  2 ─────── the spec is wrong  ·  `bench all`: no verdict, the host was busy
  4 ─────── `exec`: no such function
 75 ─────── `exec`: busy — every slot and the queue are full; retry later
111 ─────── (Mac) the VM could not be reached after three tries
125 ─────── this host cannot run the sandbox, or no supervisor is running
137 ─────── the program was killed: its deadline, or its memory limit
1–255 ───── `run` and `exec`: otherwise, the program's own exit code
CommandExit
runThe program’s code. 137 for a deadline or memory kill (use --outcome to tell which). 2 spec error, 125 host cannot run it, 1 other errors, including --pull never with no image.
execThe request’s code; 137 deadline; 75 busy; 4 unknown function; 125 no supervisor. --batch: 0 only if every line succeeded.
up1 if any function failed to start.
doctor0 if no check says FAIL. With --fix: 1 if you declined or a command failed.
bench0 within budget, 1 a budget missed, 2 (all only) no verdict.
agent test1 if any check failed.
supervisor status · stop1 if none is running · nothing to stop.
shellThe shell’s own code (130 if a signal ended it).
On a MacThe Linux command’s code; 128 + N if a signal ended it; 125 if the VM cannot be set up; 111 if it cannot be reached.

The outcome file

zygo run --outcome FILE writes, when the sandbox ends, why it ended. The exit code cannot say this alone: a deadline kill and a memory kill are both 137, and a judge or a CI step needs to know which. The file is written atomically — a temporary file, then a rename — so a reader never sees half of it. If Zygo fails before the program starts, the file is still written, with started: false and the step that failed.

{"exit_code": 137, "timed_out": false, "oom_killed": true, "peak_rss_kb": 65780,
 "wall_ms": 412.7, "plan_ms": 3.1, "start_ms": 9.4, "started": true, "phase": "run"}
FieldMeaning
exit_codeThe same number zygo run exits with.
timed_outThe deadline killed it.
oom_killedThe memory limit killed it.
peak_rss_kbThe most memory the sandbox used, in KiB.
wall_msHow long the program ran.
plan_ms · start_msTime spent planning (spec, pull, venv, network, root) · starting (up to execve).
startedWhether the program ever started.
phaseWhere it ended: plan, start, or run.

Ports and sockets

Zygo opens no port unless you run zygo api, which listens on 127.0.0.1:7700 by default. The supervisor listens only on its unix socket, which only your user can open. No sandbox mode accepts connections from outside.