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 (
sessreads/proc/<pid>/cmdlineto 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.
| State | Meaning |
|---|---|
RUNNING | A live tmux session exists for this name. |
SAVED | A valid snapshot exists, nothing live, and everything it references still looks reachable. |
STALE | A valid snapshot exists, nothing live, but something it references — currently: a pane's working directory — no longer exists on disk. |
BROKEN | The 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
dto delete it, or runsess pruneto 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]
| Key | Default | Meaning |
|---|---|---|
enabled | false | If true, every sess start <name> turns on auto-save by default — no need for --auto-save each time. |
interval | 30 | Seconds 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]
| Key | Default | Meaning |
|---|---|---|
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
| Command | Purpose |
|---|---|
start | Start a new session tracked by sess |
save | Save the current tmux session under a name |
list | List sessions with their state, size, and pane count |
open | Restore (or attach to) a saved session |
switch | Open the interactive picker |
attach | Attach to a tmux session already running |
close | Close a running session, keep its snapshot |
delete | Delete a saved snapshot only |
rename | Rename a saved session |
duplicate | Duplicate a saved session under a new name |
status | Show every session's state at a glance |
doctor | Check the health of the installation and storage |
auto-save | Start/stop background auto-save for a session |
prune | Remove corrupt or invalid saved session files |
kill-session / kill-window / kill-server | tmux-style kill commands |
| Picker | Interactive 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:
| Command | Touches the live tmux session? | Touches the saved snapshot? |
|---|---|---|
save | reads | creates/updates |
open | creates | reads |
switch | creates or reads, depending on state | reads |
close | kills | — |
delete | — | removes |
kill-session | kills | removes |
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
| Key | Action |
|---|---|
↑ / k, ↓ / j | Move selection |
Enter | State-dependent — see below |
d | Delete the selected saved snapshot |
r | Rename (type the new name, Enter to confirm, Esc to cancel) |
c | Clone under a new name (same prompt as rename) |
s | Save the tmux session sess is currently attached from (not necessarily the selected row) |
q / Esc | Quit |
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
dto 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 intoselect-layoutto 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:
tmux new-sessionfor the first window, in the first pane's saved working directory.tmux split-windowonce per additional saved pane.tmux select-layoutwith the exact layout string captured at save time, so the geometry matches precisely instead of an even/generic split.- If the snapshot has any persisted environment variables,
export NAME=valueis 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. tmux send-keysto 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.
sessrestarts 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 — seeauto-save. - No automatic retention policy yet.
sess listshows you how much each session weighs, andsess pruneremoves 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.
sessdoesn'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 inconfig.toml.sess prunetoday 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.