A terminal session daemon that keeps your shell alive when you disconnect. Like tmux or screen, but without the built-in window manager — panes and tabs are left to your terminal emulator.

Run rif dev to create a session named “dev”. Detach with Ctrl+\. Reattach later with rif dev — terminal state is restored exactly where you left off.

Why

Terminal multiplexers bundle two unrelated concerns: session persistence and window management. If you already use a capable terminal emulator (WezTerm, Kitty, Alacritty), you don’t need tmux’s panes and tabs — you need a daemon that keeps your shell running.

rif is that daemon. It does one thing: persistent sessions. Your terminal emulator handles the rest.

Usage

rif <session>                Attach to (or create) a session
rif attach <session>         Same as above
rif attach -d <session>      Create session without attaching
rif new <session>            Same as attach -d
rif list [-s]                List sessions (-s for short/scriptable format)
rif run <session> <cmd...>   Run a command in a session (-d for detached, --fish)
rif send <session> <text>    Send keystrokes to a session
rif print <session> <text>   Inject text into session display
rif write <session> <path>   Write stdin to a file via the session
rif tail <name>...           Follow session output in real-time
rif history <session>        Print session output (--vt, --html)
rif detach [<session>]       Detach all clients
rif kill <name>... [-f]      Kill sessions (-f for SIGKILL)
rif wait <name>...           Wait for sessions to complete
rif completions <shell>      Print completions (bash, zsh, fish)

All subcommands have short aliases: a, n, r, s, p, wr, t, hi, d, k, w, l/ls, c, v, h.

Workflows

Basic session persistence:

1rif dev                        # create or reattach to "dev"
2# ... work ... Ctrl+\ to detach
3rif dev                        # pick up where you left off

Background tasks:

1rif run -d build make -j8      # run in the background
2rif tail build                 # follow output
3rif wait build                 # block until it finishes
4echo $status                   # exit code is forwarded

Multi-pane projects — use your terminal emulator for splits, rif for persistence:

1# WezTerm
2wezterm cli split-pane -- rif attach project.2
3rif attach project.1
4
5# Kitty
6kitty @ launch --type=window --cwd=current rif attach project.2
7rif attach project.1

SSH:

1ssh -t server rif attach dev   # attach to a remote session

Glob matchingkill, tail, and wait support prefix globs:

1rif kill 'dev*'                # kill all sessions starting with "dev"
2rif tail 'build*'              # follow output from multiple sessions

Environment

VariableDescription
RIF_SESSIONSet inside sessions to the current session name
RIF_SESSION_PREFIXPrefix applied to session names (for grouping)
RIF_SHELLOverride the shell to spawn (default: $SHELL)
RIF_DIROverride the socket directory

Architecture

A background daemon owns a PTY and a shell. Clients connect via Unix domain sockets. The daemon runs a single poll() loop over: signal pipe, server socket, PTY master, and all client fds. Terminal state is tracked with a vt100 parser and replayed on reattach.

Sessions communicate over a binary protocol — 5-byte header (1 tag + 4 LE length) followed by payload. Daemonization uses a single fork with setsid() and /dev/null redirection. Signals (SIGCHLD, SIGTERM, SIGWINCH) are handled via the self-pipe trick to stay async-signal-safe.

The detach key is Ctrl+\ (0x1c). VQUIT is disabled in raw mode so the byte reaches the client instead of generating SIGQUIT.

Code