A terminal session daemon. Like tmux, screen, or abduco — but simpler.
rift keeps your shell alive when you disconnect. A background daemon owns a PTY and a shell; clients connect over Unix domain sockets. Detach with Ctrl+\, reattach later, and terminal state is restored.
Panes and tabs are left to your terminal emulator (WezTerm, Kitty, etc.). rift does one thing: persistent sessions.
Requires Rust (edition 2024) and just.
just # release build + install to ~/.local/bin + codesign (macOS)
just build # debug build
just release # release build only
cargo build # direct cargorift Pick a session interactively ($RIFT_PICKER or builtin)
rift <name-or-command> [...] Attach existing; otherwise run a PATH command or named shell
rift --new <command> [...] Run command in next free basename session (name, name.1, ...)
rift attach <session> Explicitly attach to (or create) a shell session
rift attach -d <session> Create session without attaching
rift new <session> Same as attach -d
rift list [-s|-v] [--where k=v] List sessions, optionally filtered by label
rift get <session> [key] Get all labels or one label value
rift set <session> k=v... Set session labels
rift unset <session> key... Remove session labels
rift clear <session> Clear all session labels
rift run <session> [<cmd...>] Run a command or piped script (-d for detached, --fish)
rift send <session> <text> Send keystrokes to a session
rift print <session> <text> Inject text into session display
rift write <session> <path> Write stdin to a file via the session
rift tail <name>... Follow session output in real-time
rift history <session> Print session output (--vt, --html)
rift logs <session> [...] Tail -f the session log file (extra args pass to tail)
rift last Attach to the most recently attached session
rift detach [<session>] Detach all clients (uses $RIFT_SESSION if no arg)
rift rename [<old_name>] <new_name> Rename a session (defaults to $RIFT_SESSION)
rift kill <name>... [-f] Kill sessions (-f for SIGKILL)
rift wait <name>... Wait for sessions to complete
rift completions <shell> Print completions (bash, zsh, fish, nu)
All subcommands have short aliases: a, n, r, s, p, wr, t, hi, lg, la, d, rn, k, w, l/ls, c, v, h.
Detach key: Ctrl+\
# Start a session named "dev"
rift dev
# Start Codex in a session automatically named "codex"
rift codex
# Start another Codex session named "codex.1" (then codex.2, etc.)
rift --new codex
# Detach with Ctrl+\, then reattach later
rift dev
# Run a command in the background, wait for it
rift run -d build make -j8
rift wait build
# Run a multiline script from stdin
printf 'cargo fmt\ncargo test\n' | rift run checks
# Send keystrokes to a running session
rift send dev "ls -la" $'\n'
# Tail output from multiple sessions
rift tail 'dev*'
# List active sessions
rift list
# Label sessions and filter the list
rift set dev project=rift env=dev
rift list --where project=riftFor bare names, an existing session takes precedence over a command with the
same name. If no session exists, Rift runs an executable found in PATH; if no
executable is found and there are no arguments, it creates a shell session with
that name. Use rift attach <name> to always request shell/session behavior.
Rift subcommand names remain reserved; use rift --new <command> to run a
same-named executable.
| Variable | Description |
|---|---|
RIFT_SESSION |
Set inside sessions to the current session name |
RIFT_SESSION_PREFIX |
Prefix applied to session names (for grouping) |
RIFT_SHELL |
Override the shell to spawn (default: $SHELL, fallback: /bin/sh) |
RIFT_DIR |
Override the socket directory (default: $XDG_RUNTIME_DIR/rift, else $HOME/.local/state/rift) |
RIFT_DIR_MODE |
Permission mode for socket directory (default: 0700) |
RIFT_LOG_MODE |
Permission mode for log files (default: 0600) |
RIFT_EMPTY_TIMEOUT |
Idle duration (in seconds) after which a detached session with 0 clients will automatically terminate (e.g., 3600 for 1 hour) |
RIFT_PICKER |
Shell command to use as session picker when rift is run with no args (e.g., fzf); receives session names on stdin, must print selection on stdout. Default: built-in numbered prompt. |
RIFT_ON_ATTACH |
Shell snippet run when a client attaches (fire-and-forget, stdio detached). $RIFT_SESSION is set and the session name is also passed as $1. |
RIFT_ON_DETACH |
Shell snippet run when a client detaches. Same context as RIFT_ON_ATTACH. |
RIFT_ON_EXIT |
Shell snippet run when the session's shell exits and the daemon tears down. Inherits the env present when the daemon was first spawned. |
When attaching to a session from multiple SSH connections or after reconnecting, rift automatically and dynamically updates your SSH_AUTH_SOCK pointer.
When the session is spawned, rift configures the shell's SSH_AUTH_SOCK to point to a stable symlink in your socket directory (<socket_dir>/<session_name>.ssh-auth-sock). Whenever a new rift client attaches, it sends its current SSH agent socket, and the daemon updates this symlink to point to the active agent. This allows commands (like git push) inside your persistent shell to seamlessly use your active SSH keys.
┌──────────┐ Unix socket ┌──────────┐ PTY ┌───────┐
│ Client │◄──────────────────►│ Daemon │◄────────────►│ Shell │
│ (rift) │ │ (forked) │ │ │
└──────────┘ └──────────┘ └───────┘
The daemon forks on first attach, creates a PTY, and spawns a shell. Both daemon and client run on a single-threaded tokio runtime (current_thread + LocalSet). The daemon's main task multiplexes the listening socket, PTY master (AsyncFd<OwnedFd>), and SIGCHLD/SIGTERM via tokio::select!; each accepted client is its own task that talks back through an mpsc channel. Terminal state is tracked via a vt100 parser and replayed to reattaching clients.
When multiple interactive clients are attached, the client that most recently
sent keyboard input owns PTY resizing. One-shot rift send and terminal
responses do not take that ownership.
Sessions are identified by name and communicate over a binary protocol (5-byte header: 1 tag + 4 LE length + payload). Framing is handled by tokio-util::codec (ipc::RiftCodec).
rift intentionally has no built-in pane/tab system. Use your terminal emulator instead:
WezTerm:
wezterm cli split-pane -- rift attach dev.2Kitty:
kitty @ launch --type=window --cwd=current rift attach dev.2The no-UI kitten gives cmd+shift+t / cmd+shift+enter a scoped way to
spawn local or SSH-backed rift panes without enabling global Kitty remote
control. On Kitty 0.43 or newer, cmd+shift+s saves only rift-attached panes
in Kitty's native session format and cmd+shift+r restores them.
See scripts/README.md for the full layout, install
commands, and daily-use table.
To automatically start or connect to a default rift session (e.g., named "main") every time you connect to a server over SSH, you can add the following snippet to your shell configuration (~/.bashrc, ~/.zshrc, or ~/.profile):
# Automatically launch/attach to a default 'rift' session on SSH login
if [ -n "$SSH_CONNECTION" ] && [ -z "$RIFT_SESSION" ] && command -v rift >/dev/null 2>&1; then
exec rift main
fiThis ensures that when you disconnect or lose your SSH connection, your processes remain running in the background, and the next time you SSH in, you will be immediately reattached to your persistent "main" session.
MIT