Skip to content

Sessions

Your panes run in a background server, not in your terminal. This page covers how that server starts and stops, how you attach to it and detach from it, how to run separate named servers or skip the server entirely, and how ws saves your session and brings it back after a crash or a reboot.

The prefix key is Ctrl-Space: prefix d means press Ctrl-Space, then d.

The first time you run ws, it starts a server in the background and attaches your terminal to it:

Terminal window
ws

The server is the same ws binary, run as ws __serve in its own session, so a hangup from your terminal doesn’t reach it. It owns every pane and every program running in them, and it draws the screen. Your terminal runs a thin client that forwards keys and mouse events and shows what the server draws. See Concepts.

When a server is already running, ws attaches to it. Given a command, ws also opens that command in a new tab, in the directory you ran it from:

Terminal window
ws claude --model opus

When no server is running, ws <command> starts a new server with that command in its first pane, in a workspace named after the directory the server started in. Plain ws (no command) does the same with a shell, and also opens the Dashboard as the first workspace and shows it, unless [dashboard] enabled = false. A restored session comes back as it was, Dashboard or not.

Programs in panes inherit the environment of the server, which is the environment of the terminal you started it from. Variables you export later, in another shell, don’t reach panes until the server restarts.

If the server fails to start within 10 seconds, ws says so and points you at the log, ~/.local/state/ws/ws.log.

Terminal window
ws attach

ws attach attaches to the running server. Plain ws does the same, but ws attach never starts a server. If none is running, it stops with this error:

no ws server is running; `ws` starts one

It also never offers to restore a saved session; use plain ws for that.

Only one client is attached at a time. If you attach from a second terminal, it takes over, and the first terminal prints:

[detached: attached from another terminal]

The screen takes the size of the terminal that is attached.

There are two ways to detach:

  • Press prefix d.
  • Close the terminal window or tab.

Either way, only the client exits. The server and every program in it keep running, including agents in the middle of a turn. After prefix d, your terminal prints:

[detached; `ws` reattaches]

Run ws (or ws attach) from any terminal to get the same screen back.

Quitting stops the server and everything in it. There are three ways:

How Asks first
prefix q, or “Quit wrkstrms…” in the menu Yes: “Quit wrkstrms and stop N panes?”
ws kill-server, from any shell No
ws kill-server --keep or ws restart No; the session is kept (see Restart)
Close the last pane (prefix x, or the program exits) prefix x on the last pane asks; a program exiting doesn’t
Terminal window
ws kill-server

ws kill-server waits until the server has stopped every pane’s programs and exited (programs that ignore the request to stop are killed after a second and a half), then prints Stopped the ws server; its panes were closed. If that takes more than five seconds, it says the server is still shutting down instead. If no server is running, it says so and exits with an error.

ws kill-server is a clean quit, like prefix q: it clears the saved session. The agents that were running are not offered for restore by the next ws, so let them finish (or write a project handoff) first. To stop the server without losing the session, use ws kill-server --keep or ws restart, below.

When the server quits, it stops whole process trees, not just the program each pane started. Closing a terminal only sends a hangup, and a program that ignores it, or a job running in the background of a shell, would outlive the pane. So ws finds every descendant process of every pane, sends each one SIGTERM, waits up to a second and a half, and sends SIGKILL to whatever is still running. Dev servers, watchers and agents’ subprocesses all stop.

A quit is a clean end: the saved session is cleared, and the next ws starts fresh. See Save and restore.

Closing a single pane, tab or workspace stops the process trees of its panes in the same way.

ws restart stops the server, starts a new one and attaches to it, and the new server puts everything back: workspaces, tabs, splits, directories, and agents resuming their conversations. Use it after installing or building a new ws, to switch to it without losing your place.

Terminal window
ws restart

What happens, in order:

  1. ws asks the server to save the session and quit. The server saves before it stops any pane, while each pane’s directory and each agent’s session id can still be read.
  2. The server stops every pane’s process tree, as a normal quit does, but leaves the saved session on disk instead of clearing it.
  3. ws waits (up to about ten seconds) for the old server to go away, starts a new one, which restores the session without asking, and attaches.

Shells come back as fresh shells in the same directory; whatever was running in them (a dev server, a build) is not restarted. Agents come back resuming their conversation (claude --resume <id>, codex resume <id>). See How each pane comes back.

ws kill-server --keep does steps 1 and 2 only:

Terminal window
ws kill-server --keep

It prints Stopped the ws server and saved the session; `ws` restores it. The next ws then offers the saved session the usual way.

Both work with named servers: ws -L dev restart. Like every command that starts a server, ws restart refuses to run from inside a pane of the server it would restart.

If the running server is older than ws restart itself, it doesn’t know the save-and-quit message, and you get the “too old” error from After you upgrade ws. Then the only way to switch is ws kill-server and ws, which doesn’t keep the session.

You can run several independent servers, each with its own panes, socket and saved session, the way tmux -L works:

Terminal window
ws -L dev

Every command takes -L <name> (or --socket-name <name>), before or after the subcommand. These are the same:

Terminal window
ws -L dev kill-server
Terminal window
ws kill-server -L dev

You can also set the name in the environment:

Terminal window
export WS_SOCKET_NAME=dev

Without a name, ws uses the server named default. Names are used as file names: letters, digits, - and _ are kept, and any other character becomes _.

Server name Socket Saved session
(none) <socket dir>/default.sock ~/.local/state/ws/state-default.db
dev <socket dir>/dev.sock ~/.local/state/ws/state-dev.db

The socket directory is $XDG_RUNTIME_DIR/ws when XDG_RUNTIME_DIR is set, and $TMPDIR/ws-<uid> otherwise (the usual case on macOS). See Configuration.

A named server is handy for trying a development build of ws without touching the server your agents run in.

Terminal window
ws --local

--local runs ws inside the current terminal, with no background server:

  • Nothing survives closing the terminal. Every pane stops with it.
  • The session isn’t saved, so there is nothing to restore.
  • prefix d has nothing to detach from and says so.
  • It listens on a socket of its own, <socket dir>/ws-<pid>.sock, so hooks, ws open and the scripting commands still work from its panes.

--local works with a command (ws --local claude) and with ws new (ws new --local <template> <name>).

Every pane has WS_SOCKET set to the socket of the server that owns it. From inside a pane:

  • ws, ws attach and ws <command> for the same server are refused, since the pane would show itself inside itself:

    already inside this ws: use prefix t for a new tab (or `ws -L <name>` for another server)
  • Attaching to another server (ws -L other) works, like nested tmux. Your prefix key goes to the outer ws; prefix Ctrl-Space sends a literal Ctrl-Space to the inner one.

  • Commands that act on the server, such as ws new, ws open, ws view --split, ws project continue and the scripting commands (ws tab new, ws split, ws pane send), go to the server that owns the pane. Pass -L <name> to ws new, ws project continue or the scripting commands to target another server instead (if it isn’t running, ws new and ws project continue start it). ws open and ws view --split act on the pane you’re in, so they always go to its server.

ws identifies each build by its binary’s path and modification time, so every rebuild or reinstall is a new build. A running server keeps running the build it started with. When you attach to a server from a different build, the top bar shows:

this server is from another ws build: `ws restart` switches, keeping the session

To switch, run:

Terminal window
ws restart

Every pane’s processes stop, and the new server brings the layout back with agents resuming their conversations (Restart). Anything running in a shell (a dev server, a test watcher) needs starting again.

Until you switch, most things keep working. A newer ws command still talks to an older server for every message that server knows. For a command the old server doesn’t know, you get:

the running ws server is too old for this command (...); install the new build, then `ws kill-server` and `ws`

This includes ws restart itself on a server from before it existed. In that case ws kill-server and ws is the way to switch, and it doesn’t keep the session, so finish or park your agents’ work first.

While it runs, the server saves the session to a SQLite file, one per server name: ~/.local/state/ws/state-<name>.db. It checks for changes once a second and saves at least every five seconds, so directory changes are picked up. The file holds:

  • every workspace, with its tabs and the active tab;
  • each tab’s name, layout, workstream, Neovim socket and linked project;
  • each pane’s command, current directory, label, and role (shell, editor or command);
  • each agent’s kind, model, session id and last status.

Scrollback and screen contents aren’t saved. Agents keep their own transcripts, and resuming brings their conversation back.

The same file keeps the history of agent hook events and status changes that the inspector and ws debug events show. That history stays after a quit.

A clean quit clears the saved session. If the server stops any other way (a crash, kill -9, a reboot, the machine losing power) or with ws kill-server --keep, the saved session is still there. The next ws that has to start a server offers to bring it back (ws restart restores without asking):

ws: the last session was saved: 2 workspaces, 5 tabs, 9 panes (3 agents).
Restore it? [Y/n]
  • Press Enter or y to restore.
  • Answer n to start fresh. The saved session is then forgotten.
  • If ws has no terminal to ask on (its input isn’t a terminal), it restores without asking, since losing work is worse.

The offer comes from ws, ws <command>, ws new and ws project continue (without --workspace, --layout or --split), whenever they start a server. If you asked for a command or a workstream, it opens on top of the restored session.

ws doctor tells you whether a saved session is waiting to be restored.

Pane Comes back as
Claude Code claude --resume <session>, with the flags it was started with (such as --model). Uses --continue if the session id never arrived.
Codex codex resume <session>, or codex resume --last if the session id never arrived.
An agent started from a shell A shell that runs the resume command, then stays open as a login shell, so exiting the agent leaves you at a prompt. For Claude Code, the saved model is passed with --model.
The tab’s Neovim nvim on the same socket. A socket file left by the crash is removed first.
Anything else (dev servers, lazygit) Its command again.
Shells A fresh shell.

Every pane starts in the directory it was last in. If that directory is gone, a workstream pane falls back to the workstream’s directory. Manual pane labels come back too.

If a pane’s program fails to start, ws starts a shell in its place so the layout stays whole. Tabs whose saved layout is damaged are dropped. Either way, the top bar says restored, with problems: ..., and the details are in the log. Otherwise it says how many workspaces and panes came back.

A workstream whose worktree was deleted can’t come back where it was. ws doctor warns about these before you restore.

Path What
~/.local/state/ws/state-<name>.db Saved session and event history, per server name.
~/.local/state/ws/ws.log The log, shared by all servers.
~/.local/state/ws/crash-<time>.log A backtrace, written when the server panics.
<socket dir>/<name>.sock The server’s socket.

~/.local/state is $XDG_STATE_HOME when that is set. The full list of files, and the environment variables that move them, is in Configuration.