psmux reads its config on startup from the first file found (in order):
~/.psmux.conf~/.psmuxrc~/.tmux.conf~/.config/psmux/psmux.conf
Config syntax is tmux-compatible. Most .tmux.conf lines work as-is.
You can also specify a custom config file path with the -f flag:
# Use a specific config file instead of default search
psmux -f ~/.config/psmux/custom.conf
# Use an empty config (no settings loaded)
psmux -f NULThis sets the PSMUX_CONFIG_FILE environment variable internally, which the server checks before searching the default locations.
Create ~/.psmux.conf:
# Change prefix key to Ctrl+a
set -g prefix C-a
# Enable mouse
set -g mouse on
# Window numbering base (default is 0)
set -g base-index 1
# Customize status bar
set -g status-left "[#S] "
set -g status-right "%H:%M %d-%b-%y"
set -g status-style "bg=green,fg=black"
# Cursor style: block, underline, or bar
set -g cursor-style bar
set -g cursor-blink on
# Scrollback history
set -g history-limit 5000
# Prediction dimming (disable for apps like Neovim)
set -g prediction-dimming off
# Key bindings
bind-key -T prefix h split-window -h
bind-key -T prefix v split-window -vpsmux launches PowerShell 7 (pwsh) by default. You can change this:
# Use cmd.exe
set -g default-shell cmd
# Use PowerShell 5 (Windows built-in)
set -g default-shell powershell
# Use PowerShell 7 (explicit path)
set -g default-shell "C:/Program Files/PowerShell/7/pwsh.exe"
# Use Git Bash
set -g default-shell "C:/Program Files/Git/bin/bash.exe"
# Use Nushell
set -g default-shell nu
# Use Windows Subsystem for Linux (via wsl.exe)
set -g default-shell wslYou can also launch a window with a specific command without changing the default:
psmux new-window -- cmd /K echo hello
psmux new-session -s py -- python
psmux split-window -- "C:/Program Files/Git/bin/bash.exe"A quoted option value is stored exactly as written, including runs of spaces and any leading or trailing space. This is the same in a config file as it is on the command line:
# Both of these store the twelve spaces
set -g status-right "left right"psmux set -g status-right "left right"Details worth knowing:
- Quotes are only stripped when they wrap the whole value.
a"b"cstoresa"b"cliterally. - Inside double quotes,
\"and\\are unescaped. Inside single quotes nothing is unescaped, so'a\b'storesa\b. - An unquoted value is taken as written but with trailing whitespace removed.
- A trailing
# commentis stripped, unless the#is inside quotes or escaped, so"#{session_name}"and"#[fg=red]"are safe. #{p<n>:}still pads at render time and remains useful for padding that should follow the rendered width rather than a fixed number of spaces.
| Option | Type | Default | Description |
|---|---|---|---|
prefix |
Key | C-b |
Prefix key |
prefix2 |
Key | none |
Secondary prefix key (optional) |
base-index |
Int | 0 |
First window number |
pane-base-index |
Int | 0 |
First pane number |
escape-time |
Int | 500 |
Escape delay (ms) |
repeat-time |
Int | 500 |
Repeat key timeout (ms) |
history-limit |
Int | 2000 |
Scrollback lines per pane |
display-time |
Int | 750 |
Message display time (ms) |
display-panes-time |
Int | 1000 |
Pane overlay time (ms) |
status-interval |
Int | 15 |
Status refresh (seconds) |
mouse |
Bool | on |
Mouse support |
mouse-selection |
Bool | on |
psmux's client-side drag selection. Set off to let in-pane TUI apps (opencode, nvim, etc.) handle their own mouse selection without psmux drawing on top |
scroll-enter-copy-mode |
Bool | on |
Enter copy mode on mouse scroll (set off to disable) |
pwsh-mouse-selection |
Bool | off |
tmux-like release-copy selection with word/line multi-click and pane-clipped extraction |
paste-detection |
Bool | on |
Detect Ctrl+V paste from console host and send as bracketed paste (set off to let Ctrl+V reach child apps like neovim) |
choose-tree-preview |
Bool | off |
Open choose-session / choose-tree pickers with the live preview pane already visible (saves pressing p). See preview.md |
bold-is-bright |
Bool | on |
Restore standard SGR codes for the 16 basic colors so the outer terminal renders bold as bright (matches a bare shell). Set off to keep explicit 256-indexed low colors byte-accurate. See Bold Is Bright |
status |
Bool/Int | on |
Show status bar (number = line count) |
status-position |
Str | bottom |
top or bottom |
status-justify |
Str | left |
left, centre, right, absolute-centre |
status-left-length |
Int | 10 |
Max width of status-left |
status-right-length |
Int | 40 |
Max width of status-right |
focus-events |
Bool | off |
Pass focus events to apps |
alternate-screen |
Bool | on |
Honour the DEC 47 / 1049 alternate screen. Set off so full screen program output lands in the scrollback instead of being discarded on exit. See Alternate Screen |
mode-keys |
Str | emacs |
vi or emacs |
status-keys |
Str | Editing style at the command prompt. Only the literal vi is checked; any other value (including unset) keeps emacs style editing |
|
copy-mode-line-numbers |
Str | off |
Line number gutter in copy mode: off, default, absolute, relative, hybrid. See Copy Mode Line Numbers |
wrap-search |
Bool | on |
Wrap copy-mode searches around the ends of the scrollback. Set off to stop at the first or last match |
renumber-windows |
Bool | off |
Auto-renumber windows on close |
automatic-rename |
Bool | on |
Rename windows from foreground process |
monitor-activity |
Bool | off |
Flag windows with new output |
monitor-silence |
Int | 0 |
Seconds before silence flag (0=off) |
visual-activity |
Bool | off |
Visual indicator for activity |
synchronize-panes |
Bool | off |
Send input to all panes |
remain-on-exit |
Bool | off |
Keep panes after process exits |
@kill-descendants |
Bool | on |
Terminate a self-exited pane shell's background children (psmux extension) |
aggressive-resize |
Bool | off |
Resize to smallest client |
window-size |
Str | latest |
largest, smallest, manual, latest |
destroy-unattached |
Bool | off |
Exit server when no clients attached |
exit-empty |
Bool | on |
Exit server when all windows closed |
set-titles |
Bool | off |
Update terminal title |
set-titles-string |
Str | Terminal title format | |
default-shell |
Str | pwsh |
Shell to launch |
default-command |
Str | Alias for default-shell | |
word-separators |
Str | " -_@" |
Copy-mode word delimiters |
activity-action |
Str | other |
Action on window activity: any, none, current, other |
silence-action |
Str | other |
Action on window silence: any, none, current, other |
bell-action |
Str | any |
Bell action: controls audible bell forwarding and status bar flag (any, none, current, other) |
visual-bell |
Bool | off |
Visual bell indicator |
allow-passthrough |
Str | off |
Allow terminal passthrough sequences (on/off/all) |
allow-rename |
Bool | on |
Allow programs to set window title via escape sequences |
allow-set-title |
Bool | off |
Allow programs to set pane title via OSC 0/2 escape sequences (see pane-titles.md) |
allow-predictions |
Bool | off |
Preserve PSReadLine prediction settings (see below) |
default-terminal |
Str | Terminal type string (sets TERM env var in panes) |
|
update-environment |
Str | (tmux defaults) | Space-separated list of env vars to refresh on client attach |
warm |
Bool | on |
Pre-spawn shells for instant window/pane creation (see warm-sessions.md) |
copy-command |
Str | Shell command for clipboard pipe | |
set-clipboard |
Str | on |
Clipboard interaction (on/off/external) |
main-pane-width |
Int | 0 |
Main pane width in main-vertical layout |
main-pane-height |
Int | 0 |
Main pane height in main-horizontal layout |
session-group |
Str | Name of the session group this session joins. none or an empty value clears it. See Session Groups |
|
command-alias |
Str | Define your own command alias as alias=expansion. See Command Aliases |
| Option | Type | Default | Description |
|---|---|---|---|
status-left |
Str | [#S] |
Left status bar content |
status-right |
Str | #{?window_bigger,[#{window_offset_x}#,#{window_offset_y}] ,}"#{=21:pane_title}" %H:%M %d-%b-%y |
Right status bar content. This is not empty by default: out of the box it renders the pane title followed by the time and date |
status-style |
Str | bg=green,fg=black |
Status bar style |
status-left-style |
Str | Left status style | |
status-right-style |
Str | Right status style | |
status-bg |
Str | Legacy convenience setter. Rewrites only the bg= part of status-style and leaves the rest intact |
|
status-fg |
Str | Legacy convenience setter. Rewrites only the fg= part of status-style and leaves the rest intact |
|
message-style |
Str | bg=yellow,fg=black |
Message style |
message-command-style |
Str | bg=black,fg=yellow |
Command prompt style |
mode-style |
Str | bg=yellow,fg=black |
Copy-mode highlight |
pane-border-style |
Str | Inactive border style | |
pane-active-border-style |
Str | fg=green |
Active border style |
pane-border-hover-style |
Str | fg=yellow |
Border style while the mouse hovers a draggable pane border |
pane-border-lines |
Str | single |
Border glyph set: single, double, heavy, simple, number, spaces, none. See Pane Border Lines |
pane-border-format |
Str | Pane border format string (e.g. #{pane_index}: #{pane_title}) |
|
pane-border-status |
Str | Pane border status position (top/bottom/off) |
|
copy-mode-line-number-style |
Str | fg=brightblack |
Style of the copy-mode line number gutter |
copy-mode-current-line-number-style |
Str | fg=yellow,bold |
Style of the line number on the copy-mode cursor row |
window-style |
Str | Style applied to the contents of every pane | |
window-active-style |
Str | Style applied to the contents of the active pane | |
popup-border-style |
Str | fg=yellow |
Border style of display-popup overlays |
popup-border-lines |
Str | single |
Popup border glyph set: single, double, heavy, rounded |
popup-style |
Str | Accepted and stored, but never read. See Accepted but Not Functional | |
clock-mode-colour |
Str | Colour of the clock-mode digits |
|
clock-mode-style |
Str | Accepted and stored, but never read. See Accepted but Not Functional | |
window-status-format |
Str | #I:#W#{?window_flags,#{window_flags}, } |
Inactive tab format. Equivalent to tmux's #I:#W#F, written out so a flagless window still reserves one space |
window-status-current-format |
Str | #I:#W#{?window_flags,#{window_flags}, } |
Active tab format. Same shape as window-status-format |
window-status-separator |
Str | " " |
Tab separator |
window-status-style |
Str | Inactive tab style | |
window-status-current-style |
Str | Active tab style | |
window-status-activity-style |
Str | reverse |
Activity tab style |
window-status-bell-style |
Str | reverse |
Bell tab style |
window-status-last-style |
Str | Last-active tab style |
psmux supports a multi-line status bar using the status-format[] array. Set the status option to a number to control how many lines the status bar displays:
# Enable a 2-line status bar
set -g status 2
# Configure each line (0-indexed)
set -g status-format[0] "#[align=left]#S #[align=right]%H:%M"
set -g status-format[1] "#[align=left]#{W:#I:#W }"The first line (status-format[0]) replaces the default status bar content. Additional lines stack below (or above, depending on status-position).
Show pane information on the border between panes:
# Enable pane border labels at the top of each pane
set -g pane-border-status top
# Customize what the label shows
set -g pane-border-format " #{pane_index}: #{pane_title} [#{pane_current_command}] "
# Disable pane border labels
set -g pane-border-status offUse select-pane -T "title" to set a pane title that appears in the border label. Clear a title with select-pane -T "". The default pane title is the hostname, matching tmux convention.
Note: PowerShell 7 automatically sets the pane title to the current working directory on every prompt via OSC escape sequences. If you see a file path in your pane border labels instead of the hostname, see pane-titles.md for details and options to control this.
pane-border-lines picks the glyph set psmux draws pane borders with. After the borders are drawn, psmux runs a junction pass that upgrades straight runs into proper corner and tee glyphs, so double and heavy borders join cleanly instead of showing mismatched crossings.
# Default: thin single lines
set -g pane-border-lines single
# Double lines
set -g pane-border-lines double
# Thick lines
set -g pane-border-lines heavy
# ASCII only, for fonts without box drawing glyphs
set -g pane-border-lines simple
# Blank borders (the gap stays, the glyphs do not)
set -g pane-border-lines spaces
# No border glyphs at all
set -g pane-border-lines nonenumber is accepted for tmux compatibility and renders the same as single. Any unrecognised value also falls back to single.
Copy mode can draw a line number gutter down the left edge. It is off by default:
# Absolute line numbers counted from the top of the scrollback
set -g copy-mode-line-numbers absolute
# Distance from the cursor row, vi style
set -g copy-mode-line-numbers relative
# Cursor row shows its absolute number, every other row shows the relative distance
set -g copy-mode-line-numbers hybrid
# Distance from the current scroll offset
set -g copy-mode-line-numbers default
# Turn the gutter off again
set -g copy-mode-line-numbers offThe gutter has two independent styles:
# Every line number except the cursor row
set -g copy-mode-line-number-style "fg=brightblack"
# The line number on the cursor row
set -g copy-mode-current-line-number-style "fg=yellow,bold"display-popup overlays and the pane contents themselves can be styled separately from the borders around them:
# Popup border colour and glyph set
set -g popup-border-style "fg=cyan"
set -g popup-border-lines rounded
# Background and foreground for every pane's contents
set -g window-style "fg=colour247,bg=colour236"
# ... and a brighter pair for the active pane, so focus is obvious
set -g window-active-style "fg=colour250,bg=black"
# Colour of the clock-mode digits (Prefix + t)
set -g clock-mode-colour cyanpopup-border-lines accepts single (the default), double, heavy and rounded. The values none and simple are accepted but render as plain single lines, because a popup always draws a border.
Note:
popup-styleandclock-mode-styleare accepted and stored but never read. See Accepted but Not Functional.
When a program inside a pane emits BEL (\x07), psmux forwards the bell character to your host terminal so you hear the audible beep. The bell-action option controls when this happens and when the status bar tab gets a bell flag (!):
# Forward bell from any window (default)
set -g bell-action any
# Forward bell only from the active window
set -g bell-action current
# Forward bell only from non-active windows
set -g bell-action other
# Mute bell completely (no sound, no status bar flag)
set -g bell-action noneThe window-status-bell-style option controls how the tab looks when flagged:
set -g window-status-bell-style "fg=red,bold"PowerShell example to test:
# These should all produce an audible beep inside psmux:
Write-Host "`a"
[Console]::Beep()
[char]7Mouse support is enabled by default. You can customize how the mouse interacts with psmux:
# Disable mouse entirely (no click, scroll, or drag)
set -g mouse off
# Disable entering copy mode on mouse scroll
set -g scroll-enter-copy-mode off
# Enable tmux-like release-copy selection with pane clipping
# Double-click selects a word, triple-click selects a line
set -g pwsh-mouse-selection onWhen pwsh-mouse-selection is on, releasing a left-drag copies the selected text immediately and clears the transient highlight. Right-click copy and Ctrl+Shift+C still work as explicit copy actions.
When scroll-enter-copy-mode is off, scrolling in a pane does not enter copy mode and instead passes scroll events directly to the running application.
Some TUI applications render their own internal layouts (multiple columns, sidebars, panels) inside a single psmux pane. Examples include opencode, lazygit, nvim with split windows, and similar dashboards.
psmux's own client-side drag selection does not know about those internal layouts, so a left-click drag inside such an app draws a selection rectangle that crosses the app's internal columns instead of respecting them.
If you would rather have the application handle mouse selection itself, disable psmux's drag selection:
# Let the app inside the pane handle its own mouse selection.
# psmux will no longer render its drag-selection rectangle.
set -g mouse-selection offWhat still works when mouse-selection is off:
- Click on a pane to focus it
- Click on a window tab in the status bar to switch to it
- Mouse wheel scrolling and scroll-into-copy-mode
- Pane border drag-to-resize
- Mouse events being forwarded to applications that request mouse tracking (DECSET 1000/1002/1003), so
opencode,htop,nvim,claude, etc. continue to receive their clicks and drags
What changes when mouse-selection is off:
- psmux no longer draws its own selection rectangle on left-click drag
- Right-click clipboard copy via psmux's selection is no longer triggered (selection never starts)
- The
pwsh-mouse-selectionword/line multi-click and release-copy behavior is suppressed too whilemouse-selection offis in effect
To restore the default behaviour:
set -g mouse-selection onYou can also toggle this at runtime without restarting:
psmux set-option -g mouse-selection off
psmux set-option -g mouse-selection on
This option is independent of mouse (which controls whether mouse events are received at all) and pwsh-mouse-selection (which only affects the style of the drag selection when it is active).
On Windows, the console host intercepts Ctrl+V, reads the clipboard, and injects the content as character events. psmux detects this pattern and reassembles it into a single bracketed paste for child applications. This is the paste-detection option and it is enabled by default.
If you use TUI applications like neovim or vim where Ctrl+V has a different meaning (visual block mode), the paste detection will intercept the keypress before it reaches the application. To let Ctrl+V pass through to the child app:
# Disable paste detection so Ctrl+V reaches child apps
set -g paste-detection offWith paste detection off, you can still paste using:
- Ctrl+Shift+V (Windows Terminal default paste shortcut)
- Right click (paste in most terminals)
- Prefix + ] (psmux paste from buffer)
psmux send-keys C-vfrom another terminal
Note:
unbind-key -n C-valone is not sufficient to stop Ctrl+V interception because the paste detection operates outside the key binding system. You must useset -g paste-detection off.
choose-session (prefix + s) and choose-tree (prefix + w) include a live preview pane that mirrors the selected session or window in real time. By default it is hidden and you press p to toggle it. To make it visible by default:
# Open all choosers with the preview pane already visible
set -g choose-tree-preview onYou can still press p inside the chooser to hide it for the current session. The setting is read once when the chooser opens, so changes to the option take effect immediately on the next open. See preview.md for the full feature documentation.
Many terminals, including Windows Terminal, render bold text in one of the 16 basic ANSI colors as the brighter variant of that color. This is the common "bold is bright" behavior, and a bare shell gets it because it emits the standard SGR codes (ESC[32m for green, brightened by ESC[1m).
psmux renders its screen through ratatui and crossterm, and crossterm serializes all 16 basic colors as the 256-indexed form (ESC[38;5;N) instead of the standard 30-37 codes. Windows Terminal only applies "bold is bright" to the standard codes, not the 256-indexed form, so colored bold text like PowerShell's $PSStyle output looked muted with a heavier font (#425). psmux rewrites those basic-color sequences back to the standard codes so bold renders bright, exactly matching a bare shell. This is on by default.
# Default: basic colors get "bold is bright" (matches a bare shell)
set -g bold-is-bright on
# Opt out: pass crossterm output through untouched
set -g bold-is-bright offThere is one tradeoff. crossterm collapses a basic color (ESC[32m) and an explicit 256-indexed low color (ESC[38;5;2m) into the identical bytes, so the rewrite cannot tell them apart and brightens both. If a program you use deliberately emits the 256-indexed colors 0 through 15 and you need them to stay exactly as sent, set bold-is-bright off. With it off, both basic and explicit 256-indexed low colors are byte-accurate, and you give up "bold is bright" on the basic colors. This is the inherent limitation of crossterm's lossy encoding; real tmux does not have it because it never collapses the two forms.
The option applies from config, at runtime, and reports through show-options and #{bold-is-bright}:
psmux set-option -g bold-is-bright off
psmux show-options -g bold-is-bright
psmux display-message -p '#{bold-is-bright}'Full screen programs (nvim, less, htop) switch the terminal to the alternate screen with DEC private mode 47 or 1049, draw over it, and switch back on exit. That is why your scrollback is untouched after you quit them. psmux honours this by default.
Setting alternate-screen off makes psmux ignore the switch, so everything those programs draw goes into the normal buffer and stays in the scrollback after they exit:
# Default: full screen apps get their own screen and leave no trace
set -g alternate-screen on
# Keep full screen output in the scrollback instead
set -g alternate-screen offThe flag lives in each pane's terminal parser, and psmux patches live panes and the warm pane when you change it, so the new value applies immediately without restarting anything.
A session group is a name shared by several sessions. psmux exposes the membership through format variables so a status bar or a script can tell grouped sessions apart:
# Join this session to the "work" group
set -g session-group work
# Leave the group again
set -g session-group noneThe group can also be named when the server is spawned:
psmux server -g workGrouped state is readable from any format string:
psmux display-message -p '#{session_group} #{session_grouped} #{session_group_size}'command-alias defines your own short name for a command:
# "dev" now means "split-window -h"
set -g command-alias 'dev=split-window -h'
bind-key D devNote: the alias is resolved by the server, so it works from a key binding, a config line, a hook,
run-shelland the command prompt. It is not resolved by the command line front end, sopsmux devtyped at a shell still fails with "unknown command". Wrap the CLI form in the real command name instead.
psmux supports tmux-style command chaining with the ; operator. Multiple commands on a single line are executed sequentially:
# Split and move focus in one binding
bind-key M-s split-window -h \; select-pane -L
# Create a development layout
bind-key D split-window -v -p 30 \; split-window -h \; select-pane -t 0In config files, escape the semicolon with \; so it is not treated as a comment delimiter.
psmux distinguishes between lowercase and uppercase letters in key bindings, matching tmux behavior:
# These are two different bindings:
bind-key t clock-mode # Prefix + t (lowercase)
bind-key T choose-tree # Prefix + Shift+T (uppercase)
# Uppercase bindings for plugin managers
bind-key I run-shell '~/.psmux/plugins/ppm/scripts/install_plugins.ps1'
bind-key U run-shell '~/.psmux/plugins/ppm/scripts/update_plugins.ps1'Multi-character key names like Space, Enter, Tab, and Escape are fully supported in prefix configuration:
set -g prefix C-Space
unbind-key C-b
bind-key C-Space send-prefix| Option | Type | Default | Description |
|---|---|---|---|
prediction-dimming |
Bool | off |
Dim predictive/speculative text |
cursor-style |
Str | bar |
Cursor shape: block, underline, or bar |
cursor-blink |
Bool | on |
Cursor blinking |
env-shim |
Bool | on |
Inject Unix-compatible env function in PowerShell panes |
claude-code-fix-tty |
Bool | on |
Patch Node.js process.stdout.isTTY for Claude Code |
claude-code-force-interactive |
Bool | on |
Set CLAUDE_CODE_FORCE_INTERACTIVE=1 in panes |
@heal-crashed-panes |
Bool | off |
Respawn a pane whose shell exits within a short grace window of being spawned, instead of closing the window. Only needed where PowerShell's non-PSReadLine fallback reader dies on its first ConPTY read |
bold-is-bright and paste-detection are psmux extensions too, but they are defined once in
All Set Options rather than repeated here. Their behaviour is explained in
Bold Is Bright and
Paste Detection.
Every *-style option and every inline #[...] block in a format string uses the same comma separated grammar:
set -g status-style "fg=white,bg=colour236,bold"
set -g status-left "#[fg=green,bold]#S#[default] "Colours. default and terminal (both mean the terminal's own default), black, red, green, yellow, blue, magenta, cyan, white, the bright variants (brightblack through brightwhite, also spelled bright-black), colour0 to colour255 (the American spelling color0 works too), #RRGGBB, idx:N, and rgb:R,G,B.
Attributes. bold, dim, italics (also italic), underscore (also underline), blink, reverse, hidden, strikethrough.
Negations. Each attribute has a matching keyword that removes it again: nobold, nodim, noitalics, nounderscore (also nounderline), noblink, noreverse, nohidden, nostrikethrough. These matter inside a format string, where a later #[...] block builds on the style already in effect rather than starting from nothing:
# Bold for the session name, then drop bold only, keeping the colour
set -g status-left "#[fg=cyan,bold]#S#[nobold] #{pane_current_command}"Reset. default or none returns to the base style of whatever is being drawn.
Style stack. push-default saves the style in effect, and pop-default restores the most recently saved one. Use them to make a temporary change without having to spell out how to undo it:
# Save the bar style, highlight the window list, then restore exactly what was there
set -g status-left "#[push-default]#[fg=black,bg=yellow] #S #[pop-default] ready"A pop-default with nothing on the stack falls back to the base style, so an unbalanced format degrades instead of breaking.
fill, align=..., list=..., nolist, range=... and norange are accepted for tmux compatibility and are handled by the status bar layout code rather than by the style parser.
Config files support tmux's %if directives, so one file can serve several machines. Conditions are ordinary format strings, which means anything you can write inside #{...} can drive a branch. A condition is true when it expands to something that is neither empty nor 0.
# A hidden variable. Later config lines can reference it as $THEME or ${THEME},
# and format strings (including %if conditions) can read it as #{THEME}.
%hidden THEME=dark
%hidden ACCENT=cyan
%if "#{==:#{THEME},dark}"
set -g status-style "bg=colour236,fg=colour250"
set -g pane-active-border-style "fg=$ACCENT"
%elif "#{==:#{THEME},light}"
set -g status-style "bg=colour253,fg=black"
set -g pane-active-border-style "fg=blue"
%else
set -g status-style "bg=green,fg=black"
%endif
# Conditions can read your own user options, and blocks nest
set -g @work-machine yes
%if "#{==:#{@work-machine},yes}"
set -g status-right "#{@work-machine} %H:%M"
%if "#{mouse}"
set -g pwsh-mouse-selection on
%endif
%endifThe directives:
| Directive | Effect |
|---|---|
%if <condition> |
Open a block. Lines inside it run only when the condition is true |
%elif <condition> |
Try another condition, but only if no earlier branch in this block matched |
%else |
Run when no earlier branch matched |
%endif |
Close the innermost block |
%hidden NAME=value |
Define NAME for the rest of the config, and for panes spawned from this session |
Notes:
- Blocks nest. An inner
%ifinside a branch that was skipped is skipped as a whole, so you never get a partly applied inner block. - The condition may be quoted with single or double quotes; the quotes are stripped before it is expanded.
- Only the first matching branch runs. Once one has, later
%elifand%elsebranches are skipped. %hiddenassignments inside a skipped branch are not applied.- The two ways to read a
%hiddenvariable are not interchangeable.$NAMEand${NAME}are substituted only on ordinary config lines, so they do not work inside a%ifcondition. Use the format form#{NAME}there, which resolves through the same session environment. %hiddenwrites into the session environment, so the name is also visible toshow-environmentand is inherited by new panes. It is not a private compile-time constant.
These options parse cleanly, survive show-options, and do absolutely nothing. They exist so that an
imported .tmux.conf loads without errors. They are listed here so you do not spend time debugging a
setting that was never wired up.
| Option | Status |
|---|---|
terminal-overrides |
An explicit no-op. The config file path parses it and throws the value away; the runtime set-option path keeps it in the user options map. Neither is ever read, because terminfo overrides have no meaning on Windows, where psmux talks to ConPTY rather than to a terminfo database. Use default-terminal to control the TERM value panes see |
lock-after-time |
Accepted and stored. Session locking is not implemented, so the timer never runs. lock-client, lock-server and lock-session exist as commands but nothing locks on a timer |
lock-command |
Accepted and stored. Never read, for the same reason |
popup-style |
Accepted and stored. Popup borders are styled by popup-border-style; the popup body itself is not styled yet |
clock-mode-style |
Accepted and stored. Use clock-mode-colour, which is read |
Setting any of these produces no warning, because the values are stored verbatim in the user options
map instead of being validated against a known option. For most of them show-options will read the
value back to you unchanged, and that is the trap: a value that round-trips is not evidence that it
took effect.
Most of psmux is configured with options, not environment variables. The variables below exist either because the setting has to be known before a config file is read, or because it is a per shell escape hatch you want for one invocation rather than forever.
| Variable | Effect |
|---|---|
PSMUX_CONFIG_FILE |
Replaces the config file search entirely. Set for you by psmux -f <file>. A leading ~ is expanded |
PSMUX_DEFAULT_SESSION |
Session name used when nothing else determines one |
PSMUX_SESSION_NAME |
Target session for a bare psmux or a control mode invocation. psmux also sets this itself when it spawns |
PSMUX_TARGET_SESSION |
Session that CLI commands address when you give no -t. Exported into panes, which is how a command run inside a pane knows where it is |
PSMUX_TARGET_FULL |
The full session:window.pane target string for CLI commands. Set by the global -t parse |
PSMUX_ALLOW_NESTING |
Set to 1 to permit running psmux inside a psmux pane. Without it the nesting guard refuses, because a nested client would fight the outer one for the console |
PSMUX_REMOTE_ATTACH |
Marks the invocation as a remote attach, which skips the bare invocation session bootstrap |
PSMUX_ACTIVE |
Set to 1 on a client process to mark that it owns the console. This is what the nesting guard reads |
PSMUX_SWITCH_TO |
Handshake variable carrying the session name across a switch-client |
PSMUX_NO_WARM |
Set to 1 to disable warm pane and warm server pre-spawning. Equivalent to set -g warm off. See warm-sessions.md |
| Variable | Effect |
|---|---|
PSMUX_CURSOR_STYLE |
Cursor shape: bar, block, underline, or default. Normally set for you by set -g cursor-style |
PSMUX_CURSOR_BLINK |
Cursor blink. 0 disables it. Normally set for you by set -g cursor-blink |
PSMUX_DIM_PREDICTIONS |
Dim PSReadLine prediction text for this shell only. The option form is prediction-dimming |
PSMUX_HOST_COLORS |
Supplies the host terminal's palette so psmux can answer OSC 4, 10 and 11 colour queries. psmux normally queries the host itself; set this when the host misreports or when the query cannot run |
| Variable | Effect |
|---|---|
PSMUX_NO_PASSTHROUGH |
Set to 1 to disable the experimental ConPTY passthrough flag on Windows build 22621 and newer. Use this if pane creation fails with ERROR_INVALID_PARAMETER |
PSMUX_PIPE_VT |
Forces pipe mode VT handling for Cygwin and MSYS style PTYs. 1 forces it on, 0 forces it off. Left unset, psmux detects the pipe itself |
PSMUX_BARE_ENV |
Spawn panes with a bare environment instead of inheriting yours. Useful when a broken inherited variable stops shells from starting |
These are exported into every pane, so scripts can detect that they are running under psmux and where:
| Variable | Effect |
|---|---|
TMUX |
Socket path and server info, for tmux compatibility. This is what most tools check |
TMUX_PANE |
Current pane id (%0, %1, and so on) |
PSMUX_SESSION |
Current session name |
Setting a variable for one shell, or for good:
# This shell only
$env:PSMUX_NO_WARM = "1"
psmux
# Every future shell
setx PSMUX_NO_WARM 1Note: psmux also has a set of
PSMUX_*_DEBUGlogging variables. They are documented in diagnostics.md rather than here, because they are for reporting a problem rather than for configuring psmux.
Use set-environment to set env vars that are inherited by newly created panes:
# Set a global env var (inherited by all new panes)
psmux set-environment -g EDITOR vim
# Set a session-scoped env var
psmux set-environment MY_VAR value
# Unset an env var
psmux set-environment -gu MY_VAR
# Show all environment variables
psmux show-environment
psmux show-environment -gEnvironment variables set this way are injected at the process level when new panes spawn, so they are completely invisible (no commands echoed in the shell).
By default, psmux disables PSReadLine inline predictions (the grayed-out autocompletion/intellisense suggestions that appear as you type) to avoid additional unexpected bugs caused by the interaction between predictions and ConPTY. This means PredictionSource defaults to None inside psmux, even if your profile sets it to HistoryAndPlugin (#150).
If enough people test predictions and the community supports enabling them by default, this will be changed in a future release.
To preserve your prediction/autocompletion settings, enable allow-predictions:
set -g allow-predictions onWith this enabled:
- If your profile sets
PredictionSource, psmux respects your choice - If your profile does not set it, psmux restores the system default (typically
HistoryAndPlugin)
Prediction dimming is off by default. If you want psmux to dim predictive/speculative text (e.g. shell autosuggestions), you can enable it in ~/.psmux.conf:
set -g prediction-dimming onYou can also enable it for the current shell only:
$env:PSMUX_DIM_PREDICTIONS = "1"
psmuxTo make it persistent for new shells:
setx PSMUX_DIM_PREDICTIONS 1You can reload your config file without restarting psmux. From the command prompt (Prefix + :), run:
source-file ~/.psmux.confOr from outside psmux:
psmux source-file ~/.psmux.confThis re-executes every line in the config file, applying any changes to options, key bindings, hooks, and styles immediately.
By default, windows and panes are numbered starting from 0. You can change the starting index for both:
# Start window numbering at 1
set -g base-index 1
# Start pane numbering at 1
set -g pane-base-index 1The pane-base-index setting affects:
- Display Panes overlay (
Prefix + q): The numbers shown on each pane start from your configured base index - Pane targets: When referencing panes by number (e.g.
select-pane -t 1), numbering follows your base index - Format variables:
#{pane_index}reflects the base index setting - Status bar and border labels: Pane numbers in format strings use the configured base
A common setup for both windows and panes to start at 1:
set -g base-index 1
set -g pane-base-index 1Press Prefix + q to show numbered overlays on each pane. While the overlay is visible, press any displayed number key to jump to that pane. The overlay auto-dismisses after display-panes-time milliseconds (default: 1000ms).
# Show pane numbers for 3 seconds
set -g display-panes-time 3000The numbers shown respect your pane-base-index setting. For example, with pane-base-index 1, three panes show as 1, 2, 3 instead of 0, 1, 2.
You can also trigger this overlay from the command line:
psmux display-panesWhen splitting panes, you can control the size and starting directory of the new pane:
# Split vertically, new pane takes 30% of the space
split-window -v -p 30
# Split horizontally, new pane takes 70% of the space
split-window -h -p 70
# Split and start in a specific directory
split-window -v -c "C:\Projects\myapp"
# Split and start in the current pane's directory
split-window -h -c "#{pane_current_path}"
# Split and run a specific command
split-window -v -- pythonThese flags also work when creating new windows:
# New window with a specific name
new-window -n "logs"
# New window in a specific directory
new-window -c "C:\Projects"
# New window running a specific command with a name
new-window -n "build" -- cargo build --watchWhen you set a window name with -n, the automatic-rename flag is turned off for that window so psmux does not overwrite your chosen name with the foreground process name. To re-enable automatic renaming for that window:
set-option -w automatic-rename onControl what happens when clients disconnect or all windows close:
# Exit the server when no clients are attached (default: off)
set -g destroy-unattached on
# Exit the server when the last window/session closes (default: on)
set -g exit-empty onWith destroy-unattached on, the server process terminates as soon as the last client detaches. This is useful for single-use sessions.
With exit-empty off, the server stays alive even after all sessions are closed, allowing new sessions to be created without restarting.
When a process inside a pane exits, the pane normally closes. To keep the pane visible after its process exits:
set -g remain-on-exit onA pane with a dead process shows its last output and can be respawned:
# Restart the default shell in the pane
psmux respawn-pane
# Kill any remaining process and restart
psmux respawn-pane -k
# Respawn in a different directory
psmux respawn-pane -c "C:\Projects"
# Respawn with a specific command
psmux respawn-pane -- python app.pyThis is useful for monitoring: if a long-running process crashes, you can see its final output and restart it without losing the pane layout.
On Unix, tmux relies on the kernel's SIGHUP delivery when a pane's terminal closes, so a process that was deliberately detached (for example with nohup) survives its pane. Windows has no SIGHUP and no pty process groups, so psmux instead walks the pane's process tree. By default, when a pane's shell exits on its own, psmux terminates any child processes the shell left behind (for example something launched with Start-Process), because otherwise those processes and their conhost.exe hosts accumulate invisibly and can exhaust the desktop heap.
If you intentionally launch background processes from a pane and want them to outlive the shell, opt out (psmux extension):
# Let background children survive when their pane's shell exits on its own
set -g @kill-descendants offNotes:
- This only affects panes whose shell exits on its own. Explicit
kill-pane,kill-window, andkill-sessionalways terminate the pane's full process tree. - With
remain-on-exit onthe pane is kept instead of pruned, so no sweep happens either way until the pane is actually closed. - Recognized off values:
off,0,false,no. Anything else, including unset, keeps the sweep enabled.
You can set environment variables at the session or global level that get inherited by all new panes:
# Set a global env var (all new panes in all sessions inherit this)
psmux set-environment -g EDITOR vim
# Set a session-scoped env var
psmux set-environment MY_VAR value
# Unset a global env var
psmux set-environment -gu MY_VAR
# View all environment variables
psmux show-environment
psmux show-environment -gYou can also pass environment variables when creating a new session:
# Create a session with custom environment
psmux new-session -s work -e "PROJECT=myapp" -e "ENV=production"The status bar supports time format variables that update in real time:
# Show current time in the status bar (updates every second)
set -g status-right "%H:%M:%S %d-%b-%y"
# Common time format variables:
# %H Hour (24-hour, 00-23)
# %I Hour (12-hour, 01-12)
# %M Minute (00-59)
# %S Second (00-59)
# %p AM/PM
# %r Full time in 12-hour format (e.g. 02:30:45 PM)
# %R Hour:Minute in 24-hour format (e.g. 14:30)
# %d Day of month (01-31)
# %b Abbreviated month name (Jan, Feb, ...)
# %Y Full year (2025)
# %a Abbreviated weekday (Mon, Tue, ...)Time variables refresh based on the status-interval option (default: 15 seconds). For second-level precision, reduce the interval:
# Update status bar every second (for live clock)
set -g status-interval 1psmux supports PSReadLine's ListView prediction style, which shows a dropdown list of suggestions:
# In your PowerShell profile ($PROFILE)
Set-PSReadLineOption -PredictionSource HistoryAndPlugin
Set-PSReadLineOption -PredictionViewStyle ListViewFor this to work inside psmux, enable allow-predictions in your psmux config:
set -g allow-predictions onWithout allow-predictions on, psmux resets PSReadLine's prediction settings during initialization, which disables ListView mode.