Introduction

sess saves the state of a terminal session — pane layout, working directories, and the commands that were running — under a name, and lets you reopen it later, even after a reboot.

Tools like tmux-resurrect or sesh already solve part of this, but they assume you're already living in the tmux plugin ecosystem (TPM and friends). sess is a single standalone binary: it uses tmux as its underlying engine, but you never have to touch tmux plugins to use it.

sess start backend-debug   # start a new tracked session
# ... work normally ...
sess save                  # save it under a name
# ... close the terminal, reboot, come back tomorrow ...
sess open backend-debug    # restored, panes and commands back where they were

Since v0.3, sess also tracks a state for every session (running, saved, stale, or broken — see Session states), can auto-save in the background, optionally persist a handful of environment variables, and check its own health with sess doctor.

This book covers installation, session states, configuration, every command, and the internals: how a session is captured, how it's restored, and the on-disk storage format.

Installation

Requirements

  • tmux installed and on your PATH.
  • Linux (sess reads /proc/<pid>/cmdline to resolve the full command line running in each pane — see How it works).

From a release binary

Download the latest tarball from the Releases page, then:

tar -xzf sess-VERSION-linux-x86_64.tar.gz
sudo mv sess /usr/local/bin/

Via cargo / crates.io

If you already have Rust installed:

cargo install sess

From source

git clone https://github.com/sess-linux/sess
cd sess
cargo build --release
sudo cp target/release/sess /usr/local/bin/

Verify it's on your PATH:

sess --version

Optional: config file

Auto-save-by-default and environment variable persistence are both off until you create ~/.config/sess/config.toml — see Configuration. Nothing here is required just to use sess.

Quick start

Start a new session tracked by sess:

sess start backend-debug

This drops you into a fresh tmux session named backend-debug. Work normally — open editors, run servers, split panes, whatever you need.

When you're ready to step away, save it:

sess save

By default this saves under the current tmux session's name. Pass a name explicitly to save it under something else:

sess save backend-debug-friday

Later — tomorrow, after a reboot, whenever — reopen it:

sess open backend-debug

sess recreates the pane layout exactly, cds each pane back to where it was, and relaunches whatever was running.

Switching between sessions

To browse everything you've saved, run sess switch (or just sess with no arguments — they do the same thing):

sess switch

This opens an interactive picker showing every session's state, pane count, and how much space it's using. Selecting one behaves according to its state: it attaches if it's already running, restores it if it's only saved, asks before restoring something stale, and shows you what's wrong if it's broken.

Closing without losing your snapshot

sess close backend-debug

Kills the live tmux session but leaves the saved snapshot untouched — useful when you want to free up the running session without discarding what you saved.

Never forgetting to save

sess start backend-debug --auto-save

Starts a background loop that periodically re-saves the session on its own. See Configuration to make this the default for every new session, and the auto-save command page for the details.

Session states

Every session sess knows about — every saved snapshot, every live tmux session, or both at once under the same name — is in exactly one of four states. This is computed from real system state, not inferred separately by each command: list, status, switch, and the picker all call the same detection logic, so they never disagree with each other.

StateMeaning
RUNNINGA live tmux session exists for this name.
SAVEDA valid snapshot exists, nothing live, and everything it references still looks reachable.
STALEA valid snapshot exists, nothing live, but something it references — currently: a pane's working directory — no longer exists on disk.
BROKENThe saved file exists but could not be parsed (corrupted JSON, usually).

RUNNING takes priority: if a live tmux session exists under that name, that's the state, even if its saved snapshot happens to look stale or broken underneath.

Where you see this

sess list      # STATE column
sess status    # grouped counts + one line per session
sess switch    # color-coded in the picker, drives what Enter does

What happens with each state in the picker

  • Running or Saved — pressing Enter opens it (attaches or restores).
  • Stale — pressing Enter shows what's missing and asks: open anyway, delete, or cancel. Nothing happens silently.
  • Broken — pressing Enter shows the parse error. There's nothing to open; use d to delete it, or run sess prune to clear out every broken snapshot at once.

Configuration

sess works with zero configuration. The config file only exists to turn on optional behavior — auto-save by default, and environment variable persistence — and lives at:

~/.config/sess/config.toml

It's entirely optional: a missing file, or one that fails to parse, just means everything falls back to its defaults. sess never errors out because of a config problem.

Full example

[autosave]
enabled = true
interval = 30

[environment]
persist = ["NODE_ENV", "EDITOR", "PROJECT_ENV"]

[autosave]

KeyDefaultMeaning
enabledfalseIf true, every sess start <name> turns on auto-save by default — no need for --auto-save each time.
interval30Seconds between auto-saves.

sess start <name> --auto-save / --no-auto-save always override this per invocation, in either direction. See start and auto-save.

[environment]

KeyDefaultMeaning
persist[]Variable names allowed to be captured on sess save. Empty by default — nothing is ever persisted unless you list it here.

See Environment variable persistence for the full picture, including the secret-name warning.

Environment variable persistence

By default, sess never captures environment variables — a snapshot's env map is empty unless you explicitly opt in.

Allow-listing variables

In ~/.config/sess/config.toml:

[environment]
persist = ["NODE_ENV", "EDITOR", "PROJECT_ENV"]

On sess save, for every name in persist that's actually set in your current shell, its value is captured into the snapshot. Names not in the list are never touched, and names in the list that aren't currently set are simply skipped.

Secrets

sess isn't a secrets manager, and doesn't try to guess which of your variables are sensitive on its own — it only ever looks at names you explicitly listed. As a safety net, though, if an allow-listed name contains a common secret-shaped substring —

*_KEY
*_TOKEN
*_SECRET
PASSWORD
AUTH
CREDENTIAL

sess save still persists it (you asked for it by name), but prints a warning so it doesn't happen silently:

warning: 'GITHUB_TOKEN' looks like it might hold a secret — persisting it
anyway because it's explicitly listed in your config.

Restoring

On sess open, persisted variables are re-exported into every pane's shell before its saved command is relaunched, so the command sees them the same way it did originally. sess prints a one-line summary of what was restored:

restoring 2 persisted env var(s): EDITOR, NODE_ENV

If a snapshot has no persisted variables — the common case — nothing is printed and nothing changes.

Commands

CommandPurpose
startStart a new session tracked by sess
saveSave the current tmux session under a name
listList sessions with their state, size, and pane count
openRestore (or attach to) a saved session
switchOpen the interactive picker
attachAttach to a tmux session already running
closeClose a running session, keep its snapshot
deleteDelete a saved snapshot only
renameRename a saved session
duplicateDuplicate a saved session under a new name
statusShow every session's state at a glance
doctorCheck the health of the installation and storage
auto-saveStart/stop background auto-save for a session
pruneRemove corrupt or invalid saved session files
kill-session / kill-window / kill-servertmux-style kill commands
PickerInteractive TUI, run with no arguments

Every command also responds to -h / --help for a quick reminder.

save / open / switch / close / delete / kill-session, at a glance

These six overlap in what they touch, so here's the full picture in one place:

CommandTouches the live tmux session?Touches the saved snapshot?
savereadscreates/updates
opencreatesreads
switchcreates or reads, depending on statereads
closekills
deleteremoves
kill-sessionkillsremoves

close then delete is the deliberate two-step way to fully tear down a session; kill-session is the one-shot shortcut for the same end result.

sess start

sess start <name> [--auto-save] [--no-auto-save]

Starts a new tmux session tracked by sess, without needing to already be inside tmux. If a live tmux session with that name already exists, sess attaches to it instead of creating a duplicate.

sess start backend-debug

Auto-save

sess start backend-debug --auto-save      # force it on for this session
sess start backend-debug --no-auto-save   # force it off for this session
sess start backend-debug                  # follows [autosave] enabled in config.toml

--no-auto-save always wins if both are somehow relevant; otherwise --auto-save wins; otherwise the configured default applies. See auto-save for what the background loop actually does.

sess save

sess save [name] [--force]

Saves the current tmux session (the one sess is being run from) under a name. Defaults to the current session's own name if none is given.

sess save
sess save backend-debug-friday
sess save backend-debug-friday --force   # overwrite without prompting

If a saved session already exists under that name, sess asks for confirmation before overwriting — unless --force is passed. sess never overwrites silently.

If [environment] persist is configured, this is also the point where allow-listed variables are captured (with a warning for any that look like secrets).

Must be run from inside a tmux session; if it isn't, sess fails with a clear message pointing you to sess start.

Note: if you run sess save from inside one of the panes it's saving, that pane's "current command" at the instant of capture is sess itself — sess detects this and treats it the same as an idle shell (nothing to relaunch), rather than recording a self-referential command that would re-run itself on restore.

sess list

sess list [--json]

Lists every session sess knows about — the union of saved snapshots and live tmux sessions — with its state, pane/window count, and on-disk size, sorted by name.

sess list
NAME                 STATE     WIN PANE       SIZE  SAVED
backend-debug        RUNNING     1    3      633 B  2026-08-30 03:16
scraper-mimir        STALE       1    2      1.2 KB  2026-08-29 22:04
  a saved directory no longer exists: /tmp/old-clone

A STALE or BROKEN entry prints a second, indented line explaining why — the same detail shown in the picker and in sess status.

Pass --json for the same data as JSON, for scripting:

sess list --json

sess open

sess open <name> [--force]

Reopens a saved session. If a live tmux session with that name is already running, sess attaches to it instead of restoring — your saved snapshot is left untouched.

sess open backend-debug

Pass --force to kill the live session first and restore fresh from the saved snapshot instead of attaching to what's currently running.

sess open backend-debug --force

If the snapshot has any persisted environment variables, sess prints a one-line summary of what it restored. Selecting a session by state (running/saved/stale/broken) instead of by name directly? See switch and Session states.

sess switch

sess switch

Opens the same interactive picker as running sess with no arguments — this command exists mainly so switch reads naturally as "the fast way to jump between sessions" if that's how you think about it. Behavior is identical either way; see Picker (TUI) and Session states for how selecting an entry behaves depending on its state.

sess attach

sess attach <name>

Attaches to a tmux session that's already running. Fails with a clear message if it isn't — pointing you to sess open <name> to restore it from a saved snapshot instead.

sess close

sess close <name>

Kills the live tmux session but leaves its saved snapshot untouched — the counterpart of open. close then open round-trips back to the same state (modulo whatever changed while it was closed, obviously).

Fails with a clear message (naming the session's actual state) if <name> isn't currently running.

If you want to close and forget it in one step, use kill-session instead.

sess delete

sess delete <name>

Deletes a saved session's snapshot only. As of v0.3 this does not touch a live tmux session with the same name — if one is running, sess tells you so and points you at close:

saved session 'backend-debug' deleted.
note: 'backend-debug' is still running as a live tmux session — use `sess close backend-debug` to stop it too.

For the previous all-in-one behavior (kill the live session and delete the snapshot), use kill-session instead — see the overlap table for the full picture of how these relate.

sess rename

sess rename <from> <to>

Renames a saved session. Fails if the destination name is already taken, or if source and destination are the same.

sess duplicate

sess duplicate <from> <to>

Duplicates a saved session under a new name, leaving the original intact. Useful for branching off a known-good session before experimenting.

sess status

sess status

A quick summary of every session's state: counts up top, then one line per session with its state and (for stale/broken ones) why.

1 running, 2 saved, 1 stale, 0 broken (4 total)
  RUNNING  backend-debug
  SAVED    scraper-mimir
  SAVED    onboarding-call
  STALE    old-project — a saved directory no longer exists: /tmp/old-clone

Uses the exact same state detection as list, switch, and the picker — nothing here is computed differently.

sess doctor

sess doctor [--fix]

Runs a fixed set of health checks and prints one line per check:

✓ tmux installed
✓ tmux version (tmux 3.4)
✓ storage directory (/home/user/.local/share/sess)
✓ storage writable
✓ session data valid
✓ terminal environment
  • — fine.
  • — worth knowing about, but not blocking (e.g. some saved sessions are stale).
  • — something is actually broken (e.g. tmux isn't installed, or a saved session file can't be parsed).

Exits with status 1 if any check reports , 0 otherwise — safe to use in a script.

--fix

sess doctor --fix

Applies only safe, non-destructive repairs — currently, (re)creating the storage directory if it's missing. doctor --fix never deletes data; use prune explicitly for that.

sess auto-save

sess auto-save <name> [--interval N]
sess auto-save <name> --stop

Starts (or stops) a background process that periodically re-captures and re-saves <name> under itself, so recent state isn't lost if you forget to run sess save yourself before closing the terminal.

sess auto-save backend-debug                # every [autosave] interval seconds (default 30)
sess auto-save backend-debug --interval 60  # override the interval for this one
sess auto-save backend-debug --stop         # stop it

The session must already be running (sess auto-save doesn't start one — use sess start <name> --auto-save for that in one step).

The loop exits on its own once the tmux session it's watching disappears, and it preserves any persisted environment variables from the last manual sess save rather than dropping them.

See Configuration to make this the default for every new session instead of calling it separately.

Known limitation: the background process is a plain detached child, not a fully daemonized one (no double-fork / setsid). Robust on most Linux setups, but see Limitations.

sess prune

sess prune

Scans the storage directory and removes any saved session file that's corrupt or fails to parse, instead of leaving it to break sess list silently. Reports how many files were removed.

sess kill-session / kill-window / kill-server

Thin, tmux-flavored wrappers for killing things directly, without leaving sess:

sess kill-session <name>
sess kill-window <target>
sess kill-server

kill-session also deletes the matching saved snapshot, if one exists — this is the one-shot equivalent of close followed by delete, not the same as either on its own. Double-check the name before running it; see the overlap table if you want the exact difference from close/delete spelled out.

Picker (interactive TUI)

sess
sess switch   # identical — see switch.md

Shows every session sess knows about with its state, pane count, a relative size bar, and its exact on-disk size, plus a running total at the top.

Keys

KeyAction
/ k, / jMove selection
EnterState-dependent — see below
dDelete the selected saved snapshot
rRename (type the new name, Enter to confirm, Esc to cancel)
cClone under a new name (same prompt as rename)
sSave the tmux session sess is currently attached from (not necessarily the selected row)
q / EscQuit

Rename, clone, delete, and save all happen in place — the picker stays open and the list refreshes, so you can chain several actions without leaving it. Only opening a session exits the picker (attaching hands the terminal over to tmux).

What Enter does, by state

  • Running or Saved — attaches or restores immediately.
  • Stale — shows what's missing and asks: [o]pen anyway, [d]elete, or [c]ancel. Nothing happens until you choose.
  • Broken — shows the parse error; press any key to dismiss. There's nothing to open — use d to delete it.

How it works

sess doesn't reinvent terminal multiplexing — it uses tmux as its engine and adds three things on top: a capture step, a storage format, and a restore step. This section covers each.

Capturing a session

When you run sess save, it reads the current tmux session's structure via tmux list-windows and tmux list-panes, collecting for each pane:

  • its working directory (pane_current_path)
  • the exact layout string tmux uses internally (window_layout), which is later fed straight into select-layout to reproduce the geometry exactly
  • what's running in it

Full command lines, not just process names

tmux's pane_current_command only reports a process's short name — sleep, not sleep 300. Restoring with just the name loses all arguments, so sess resolves the real, full command line by reading /proc/<pid>/cmdline for the process actually running in the foreground of that pane (found by walking the tmux pane's shell process's children until one matches the name tmux already identified). This only works on Linux.

Idle shells (bash, zsh, fish, sh, dash with nothing running) are recorded as empty — there's nothing to relaunch, sess just returns the cursor to that directory on restore.

Capturing sess itself

A pane running sess save for its own session is a special case: at the instant of capture, that pane's foreground process is sess itself (it hasn't returned to the shell prompt yet). Recording that as a "command to relaunch" would mean restoring the session later silently re-runs the sess save invocation — potentially resurrecting an old snapshot with --force. sess detects this (comparing against its own binary name) and treats it the same as an idle shell.

Environment variables

If [environment] persist lists any variable names, those that are currently set are captured into the snapshot's env map at this point — nothing else in the environment is ever touched. See Environment variable persistence for the secret-name warning that applies here.

Restoring a session

sess open rebuilds the session window by window:

  1. tmux new-session for the first window, in the first pane's saved working directory.
  2. tmux split-window once per additional saved pane.
  3. tmux select-layout with the exact layout string captured at save time, so the geometry matches precisely instead of an even/generic split.
  4. If the snapshot has any persisted environment variables, export NAME=value is sent into every pane's shell first — each pane is a separate process, so a var exported in one pane never reaches the others on its own.
  5. tmux send-keys to relaunch each pane's saved command — guarded with -- so a command that happens to start with - is never misread as a flag.

If a live tmux session with the target name already exists, sess refuses to restore over it and attaches instead (see open for the --force override).

Restoring relaunches commands from scratch — it does not preserve a process's internal memory state (variables loaded in a REPL, for example). See Limitations.

Attaching, for real

Handing control of the terminal back to tmux for an interactive attach needs the child process to inherit the real stdin/stdout/stderr, not have them captured into a pipe — sess uses Command::status() for this one call specifically (Command::output(), used everywhere else for its convenient error capture, would silently break interactive attach).

Storage format

Each saved session is one JSON file at ~/.local/share/sess/<name>.json — no database, no binary format.

{
  "version": 2,
  "name": "backend-debug",
  "created_at": "2026-08-30T03:16:25Z",
  "windows": [
    {
      "index": 0,
      "name": "dev",
      "layout": "b25f,80x24,0,0,2",
      "panes": [
        { "index": 0, "cwd": "/home/user/project", "command": "sleep 300" }
      ]
    }
  ],
  "env": { "NODE_ENV": "production" }
}

This is what sess list reports the size of — it's typically a few kilobytes, since it stores layout and command lines, not process memory or scrollback.

Versioning and backward compatibility

version and env were both added in v0.3. Snapshots saved by earlier versions of sess — without either field — still load without any migration step: both default sensibly on read (version defaults to 1, env defaults to an empty map). If a future change to the format ever needs an actual migration instead of a safe default, it'll bump CURRENT_SNAPSHOT_VERSION and add an explicit conversion step rather than break old snapshots outright.

One reader for raw files

Every saved .json file is read through a single function (storage::list_all_raw) that returns, per file, either the parsed snapshot or the parse error as text — never silently skipping a broken one. sess list, sess prune, and the state detection all build on top of this same read path instead of re-parsing independently, so a corrupted file is reported the same way everywhere it shows up.

Limitations

  • Commands are relaunched, not resumed. sess restarts each saved command from scratch — it does not preserve a process's internal state (variables loaded into a REPL, an in-progress download, and so on). Auto-save (v0.3) helps you lose less by saving more often, but it's not a substitute for real process checkpointing — see Roadmap for where that stands.
  • Linux only. Resolving a pane's full command line (with arguments) relies on reading /proc/<pid>/cmdline.
  • Auto-save's background process isn't a full daemon. It's a plain detached child (no double-fork / setsid), which is robust on most Linux setups but not guaranteed to survive every possible parent-exit scenario the way a proper daemon would. Kept simple on purpose — see auto-save.
  • No automatic retention policy yet. sess list shows you how much each session weighs, and sess prune removes files that are outright corrupted, but nothing ages out saved sessions automatically just for being old or large. Cleanup of otherwise-valid sessions is still manual (sess delete).
  • Environment persistence is opt-in and flat. sess doesn't do anything clever with variable scoping or per-window environments — it's a single allow-listed set of name/value pairs per session. See Environment variable persistence.

Roadmap

Auto-save, environment persistence, session states, and sess doctor shipped in v0.3. What's still open:

  • Retention policy / automatic gc. Time-decaying snapshot retention (recent saves kept in full, older ones thinned automatically), plus caps configurable in config.toml. sess prune today only removes files that fail to parse — it doesn't do age- or size-based cleanup.
  • Experimental process checkpointing (CRIU). A genuinely different mechanism from auto-save: instead of relaunching a saved command from scratch, actually freeze and later resume the real process, memory included. Investigated for viability — CRIU itself is mature technology (used for container live-migration and HPC job preemption) and handles pseudo-terminals well, but it requires root/sudo each time, and needs matching system library versions between save and restore, which makes it unreliable across anything longer than "pause for a bit before a reboot." If it ships, it would be strictly opt-in (--freeze) with automatic fallback to the normal relaunch behavior whenever it can't restore cleanly — never the default.