Skip to content

Socket protocol ​

The JSON protocol on the server socket, including moving sessions between servers.

From other programs: the socket ​

Send one line of JSON to the server socket (~/.zeropty/mux.sock; change it with ZEROPTY_SOCK) and it answers with one line. One connection can carry many commands, and answers come back in order. The socket has 0600 permissions, so only the same user (and root) can connect.

→ {"argv":["capture-pane","-p","-t","=work:"],"cwd":"/"}
← {"code":0,"out":"...screen...","err":""}
  • cwd is the base directory for relative paths (new-session -c, load-buffer).
  • If the request includes "raw":true, the answer is not JSON but a line <code> <out byte count> <err byte count> followed by the raw out and err bytes. This is for clients that have a hard time parsing JSON (the zsh function).
  • An attach connection exchanges frames after the first answer: [kind u8][length u32 LE][bytes]. Server to client: 0 (output), 1 (session ended). Client to server: 0 (keys), 1 (size: cols and rows as u16 LE), 2 (detach).
  • A connection whose first request is export-sessions or import-sessions is used to hand sessions over (internal): socket protocol.
sh
# From any language: for example, with nc
printf '{"argv":["list-sessions"],"cwd":"/"}\n' | nc -U ~/.zeropty/mux.sock

Moving sessions ​

Moves sessions to another process. Running programs keep running (even the pid stays the same). The screen and scrollback are transferred as bytes, and the session's fds by fd passing (SCM_RIGHTS).

sh
zeropty pull-sessions -s ~/.zeropty/other.sock [name...]   # another server's sessions into this one (all if no names)
zeropty push-sessions -s ~/.zeropty/other.sock [name...]   # this server's sessions to another server
zeropty restart-server                                  # a new server takes over every session and the socket
  • The same commands work on an embedded engine. For example, a Node app can pull-sessions from the server when it starts and push-sessions them back when it exits, so sessions survive app restarts.
  • restart-server starts the new server from the zeropty binary you just ran. That makes it the way to upgrade without losing sessions. Commands that arrive during the few ms of the switch go to the old server.
  • The sending side releases only the sessions the receiver confirmed it took. If anything fails midway, the sessions stay where they were. Sessions whose name already exists on the other side are not moved.
  • Limitation: if an escape sequence was half received at the moment of the move, the rest of it is printed as text.

Socket protocol: export-sessions, import-sessions ​

These are internal requests that pull-sessions, push-sessions and restart-server use on the server socket. You will rarely send them yourself, and the session record format may change between versions (the first byte is the record version, and a record from another version is rejected with session record of another version). This section is kept as a human-readable reference. Implementation: src/engine/handover.rs.

  • If the first request line of a connection is {"argv":["export-sessions", name...],"cwd":""} or {"argv":["import-sessions"],"cwd":""}, the connection is used only to hand over sessions and is closed when done. Meanwhile the server does nothing else (usually a few ms).
  • Each side waits up to 10 seconds per step.
  • Each session is sent as [record length u32 LE][record]. The session's fds (whichever of input and output are open, 0~2 of them) are attached to the 4-byte length with SCM_RIGHTS. A length of 0 marks the end. A record may not exceed 256MB.
  • Every other line is a single line of JSON.
export-sessions (taker → server)
→ {"argv":["export-sessions","a","b"],"cwd":""}     all sessions if no names
← [length][record] ... [0]                          each session with its fds
← ["b"]                                             names the server does not have
→ {"taken":["a"],"failed":[]}                       sessions taken; the server releases only these

import-sessions (giver → server)
→ {"argv":["import-sessions"],"cwd":""}
← "ready"
→ [length][record] ... [0]
← {"taken":["a"],"failed":["duplicate session: b\n"]}   the giver releases only the taken ones

failed holds the reason a session was not taken (a session with the same name already exists, a different record version, and so on). If the connection drops or the format is wrong at any step, the receiver releases the sessions it took, and the sessions stay on the original side.

Released under the MIT or Apache-2.0 license.