Skip to content

Manual page

What man tmux-companion shows, rendered from docs/tmux-companion.1.

TMUX-COMPANION(1) General Commands Manual TMUX-COMPANION(1)

tmux-companion — a status line, a set of pickers and a project manager for tmux

tmux-companion command [flags]

tmux-companion is one binary in two modes.

server binds a unix socket and answers requests forever, holding the caches that make a status line cheap. Every other command is a client: it connects, starting the server when the socket is absent, writes one JSON line, reads one back, prints it and exits.

The point of the split is the status bar. tmux gates ‘#()’ to status-interval per attached client, and a spawn costs 12.4 ms of CPU before the command has computed anything, so the bill is the number of distinct commands rather than what they do. status-right answers with git, bandwidth and battery in one call.

Commands that draw on a terminal -- the pickers, the dialogs -- run in the client process. The daemon has no terminal; it answers with rows and the client draws them.

One daemon answers for one socket. A start takes an exclusive lock on <socket>.lock before it touches the socket file, and a start that cannot take the lock exits quietly, leaving the daemon that holds it to answer. Without the lock two starts in the same moment both unlink the socket file and both bind, and the one whose file was taken away keeps running where no client can reach it.

A daemon exits when the socket file it bound is deleted or replaced, which it checks every thirty seconds. That is the only thing that ends a daemon besides a signal or __shutdown, and it is what stops an abandoned daemon from outliving the socket it was started for. The next client starts a fresh one, which costs one cold git(1) status.

__shutdown removes the socket file on its way out, so a path with no socket file means no daemon rather than a daemon that has stopped answering.

[directory] [--last, --hook]
The way in, from a shell that is not in tmux yet. Opens the project picker, live sessions first and every directory the configured source knows underneath, and attaches to what is chosen. The source is zoxide(1) unless [project] dirs_source says otherwise. --last goes back to the session used most recently without asking, and a directory skips the picker. Inside tmux it switches rather than attaching.

‘tmux’ on its own leaves you in a session called ‘0’ holding one bare shell, which is what this replaces. --hook is for the client-attached hook in tmux.conf, so that plain ‘tmux’ lands in the same picker: it opens only when the session is one tmux named itself, all digits, with one window, one pane and a shell in it, and leaves any other session alone.

range
What a mouse click on a segment of the right side does. The agents and health segments carry a ‘range=user|NAME’ mark, the way tmux marks its own window list, so a ‘MouseDown1StatusRight’ binding of ‘run-shell tmux-companion click '#{mouse_status_range}'’ reaches this with the segment's name; it runs what [[status.right.segments]] on_click says for that segment, or the default: the inbox for the agent count, the brief for the health mark, nothing for the rest.
[--print, --hook]
What needs you, on one screen: the agents waiting and what each one asked, the reasons the health mark is up, the sessions nobody has touched for three days, how long a sitting has run when the chunk clock is on, quiet hours when they are on, and one line of numbers with when the last snapshot was. Everything on it is a command of its own; this is the one look on sitting down, and somewhere to act from.

In a terminal it waits for keys, and a footer names the ones that do something right now:

number
go to that waiting agent's pane, or to that idle session; the numbers run on from the agents into the idle sessions, and with ten rows or more a digit that could start a longer number waits for the next digit or enter
number
close that idle session, asking first, its layout saved, as sessions idle does
acknowledge the health reasons, as health ack does
show the doctor report, and come back on enter
open the inbox picker
open today's journal picker
save a snapshot, as sessions save does
an hour of quiet, or with quiet on, the end of it
, Esc, Enter
close

Going somewhere, and i and j, end the brief. The rest say one line about what they did and draw the brief again. --print prints and exits, with no numbers and no keys. --hook is for the client-attached hook: it opens the popup only when something is waiting or wrong, and says nothing when the server is quiet. In the week after a build first runs, a line counts the setup items still open, when there are any.

[--print]
Lists what the tool offers and the state of each: ‘on’, ‘open’, ‘off-by-choice’, ‘skipped’ or ‘cant-tell’. Enter copies the lines to the clipboard and asks before adding them to tmux.conf or config.toml. Ctrl-x skips a row. Ctrl-e sets a binding's key. --print prints id, group, state, key and line, tab-separated.

These print tmux format strings on standard output and are meant for ‘#()’ in tmux.conf.

[path] [--style style, --branch-max-len n]
Git, bandwidth and battery in one call, computed concurrently, and the agent count and the health mark when [status.right] asks for them. The health mark is one glyph and a word when a timer failed in the last hour and hasn't worked since, config.toml changed after the daemon started, or the binary on disk is newer than the daemon running; nothing when the daemon is fine. With [online] enabled it also reads ‘offline’ when two TCP connections in a row to [online] probe went unanswered, [online] interval_secs apart; it is off until turned on, since the probe reaches an address outside the machine. One ‘#()’ rather than three. The agents segment reads ‘4 agents • 1 waiting’: how many panes run one of [agents] programs, and how many of those have drawn nothing for [agents] waiting_secs. [agents] style set to ‘glyphs’ draws the robot and the count, then an arrow, an hourglass and the waiting count, with nothing spelled out. [agents] show picks the count after the total: ‘waiting’, the agents that have stopped, whether a hook said asked or done, the bell rang, or the window went quiet; ‘busy’, the agents that are working, drawn with the progress glyph, for somebody who keeps a dozen open and works one at a time; or ‘both’. It draws nothing when no agent is running, and the pane list behind it is read at most once per [agents] interval_secs however many clients are attached, and never when the segment is not configured. Takes a path and deliberately no pane pid: a pid makes the git segment enumerate every process on the machine, which cost 18.5 ms of its 26.0. --style is one of ‘fill’, ‘outline’ or ‘outline-bright’, and anything else is refused with that list. --branch-max-len middle-ellipsizes a branch name longer than n; without the flag [git] branch_max_len decides, 20 out of the box.
[path] [pid] [--no-daemon --no-tmux]
The git segment on its own: branch, what is dirty, ahead and behind, with the same --style and --branch-max-len. With a pid it also marks a suspended editor. A daemon that answers with an error exits this command non-zero, which tmux ignores in a ‘#()’ and a prompt or a script can read.
Charge, as an icon and a percentage. Reads the platform's battery and prints nothing on a machine without one.
[--no-daemon --no-tmux]
Bytes per second in and out, over all interfaces but the loopback. A rate, so it is never cached. Draws nothing below [network] threshold_bps.
pane-pid
What is stopped or running under a pane, from the table in [[sh_jobs.job]].
attached active
A marker when more than one client is looking at this session.
-i index [flags]
One window's entry for window-status-format. Costs a process spawn per window per redraw, and tmux redraws on pane output as well as on the timer, so this is the expensive way to draw a window list.
Sample git segments in every colour style, without a server.

gst and net are ordinary commands that print a string, and neither needs tmux to be running. Two flags make them usable anywhere a shell prompt, a bar or a script wants them.

Write ANSI escapes instead of tmux's ‘#[fg=...]’ markup, and reset the terminal at the end so nothing after the segment inherits its colour. A segment that drew nothing prints nothing, not even a newline.
Compute the answer in this process and exit. No socket is opened, no server is started and nothing is left running. The cost is the cache: every call pays for a cold git(1) status, about 51 ms against a large tree, where the daemon answers a warm one in well under a millisecond. That is the right trade for a shell prompt and the wrong one for a status bar refreshing once a second.

net is a rate and needs two counter readings, so without a daemon to hold the first one it is kept in $XDG_STATE_HOME/tmux-companion/net-sample. The first call after a reboot records the reading and draws nothing, which is what the daemon does on its first call and for the same reason.

For a prompt, in bash(1) or zsh(1):

PS1='$(tmux-companion gst --no-daemon --no-tmux) $ '

Inside tmux, leave both flags off: the daemon is what makes the segment cost almost nothing to draw once a second.

Each is a fuzzy-matched list with a preview. They are bound to keys in docs/tmux.conf.full.example; the bindings shown are that file's.

Every picker takes the same keys. Typing filters, the arrows and Page Up and Page Down move, enter takes the row, and escape, ctrl-c or ctrl-d closes it. Ctrl-a, ctrl-u or Delete clears the filter, ctrl-p or Tab changes where the preview sits, ctrl-j and ctrl-k scroll it, and alt-enter takes what was typed rather than the row. The terminal's cursor sits on the selected row, which is what a screen reader or a braille display follows, and on the end of the filter when nothing is listed. Nothing in a picker is timed.

[directory] [--print]
Live sessions and the directories the configured source knows, in one list, each in its project's colour. The source is zoxide(1) by default and [project] dirs_source changes it; a source that is not installed lists nothing rather than failing, and a directory it has never seen can still be typed in full. Picking a live session switches to it; picking a directory builds its session from [[layout]]. A live session nobody is attached to that has been quiet for a day or more carries an ‘idle’ column with its age, so the stale ones stand out without leaving the list; sessions idle is the one that closes them. --print lists the rows and exits, and opens nothing; it cannot be combined with a directory. Bound to ‘M-s’. Sessions are listed most recently attached first, so the second row is the session you were in before this one, and ‘M-s’ in the picker switches to it at once: ‘M-s M-s’ goes back. It does nothing when there is no other session.
project save [--no-commands]
Capture this session's windows and panes as the layout this project opens with. A saved layout wins over the config. The save is all or nothing: if tmux cannot be read, or answers with a line this cannot parse, nothing is written and the layout already on disk is left alone, because a layout quietly missing a pane is worse than one that is out of date. The file is written through a temporary name and renamed over the old one, so a save interrupted halfway leaves the previous layout rather than half of a new one. Bound to ‘prefix S’.
project forget [directory]
Delete the saved layout, so the config decides again. The project is the session this runs in, or the directory given. Bound to ‘prefix M-S’.
project show [directory]
Which layout this project gets, which file decided, and the windows it opens. The order is a saved layout, then the checkout's own .tmux-companion.toml, then [[project.override]], then [project] layout. A saved file that is there and not used is named with the reason, whether it does not parse or an older build wrote it wrong, and so is a checkout file; a checkout file whose commands were blanked because the project is not under [project] trusted says so. The project is the session this runs in, or the directory given.

close-project is the old name for project close and still works for one release.

[--skip-pane-history] [--exclude names]
Capture every session on the server as a new generation under the state directory. Where project save keeps one file per project, overwritten on demand and edited by hand, this keeps generations of the whole server and throws the oldest away: the first answers what a project looks like, the second what you were doing at the time. The save is all or nothing, like a project's: a line of tmux output this cannot read stops the write rather than shrinking the snapshot. A note left on a pane with note is captured with it and put back by a restore. --skip-pane-history records the sessions and leaves out what was on each pane's screen, which is the expensive half and the half holding whatever you printed. --exclude adds to [sessions] exclude rather than replacing it, and a session left out is one that does not come back.
[file] [--stamp stamp]
A generation as one file that reads on another machine: every path under the home directory is spelled ‘~’, the project colours from _project-map.tsv travel with it, and what was on each pane's screen is left behind. The newest generation unless --stamp names one; printed when no file is given.
file [--no-map]
The file sessions export wrote, stored here as a new generation with this machine's home in place of ‘~’, and the project colours it names added for the projects this machine has not coloured yet; --no-map leaves the map alone. Then sessions resurrect brings it up and says which directories are not here. The themes themselves come from theme init.
[stamp] [--only names] [--exclude names] [--merge] [--dry-run] [--yes] [--detach]
Rebuild a server from a generation, defaulting to the newest. With no generation of its own to read, it reads tmux-resurrect's newest save instead, so a machine with months of those loses none of them on the day it switches over. A snapshot written by a newer build than this one is read anyway, with one line on standard error saying the restore may miss what that build knew. Refuses outright when the server already holds sessions, naming them, because a restore that half-landed on work somebody was doing is the failure worth being careful about. --merge adds the sessions that are missing and never touches one that is running.

What each pane gets told to run comes from [[restore.program]], which is default deny: a command no row claims opens the pane and says so rather than running it. So does a command the capture had to guess at, since its arguments are already gone and running it would bring an agent back in the wrong conversation. --dry-run prints the exact tmux commands and exits, and they are the commands that would run rather than a description of them. It prints the waits too, as comments, because a restore is not only tmux calls.

When the restore does not know what a pane should run, and somebody is watching, it shows what it is about to do and counts down before going ahead. It opens for a pane no row claims, one a row refused, and one whose command the capture read off a running process so the arguments are already gone. It opens too when the daemon before this one crashed, once: a restore that goes ahead clears the crash, and a dry run or a cancel leaves it for the next. It never opens on a count of panes: a restore where everything is known goes straight through however many there are, because a confirmation on every restore is one people turn off. Any key stops the clock, ‘tab’ runs one of the listed panes after all, ‘a’ takes all of them and ‘q’ cancels. [sessions] confirm_secs sets the countdown and zero never draws it. [sessions] confirm_wait draws it with no countdown and waits for a key, whatever confirm_secs says. --yes runs everything the table claimed without asking; whatever is not run still opens its pane, in the right directory, at a prompt.

Before a pane is told to run anything it has to have drawn a prompt, which tmux knows only when the shell tells it, which is what shell-init makes the shell do. A shell that emits the mark answers in milliseconds; one with no shell-init line in its rc file costs two seconds and is then typed into anyway, which is what a restore did before the marks existed.

It attaches at the end when somebody is watching, which means stdin is a terminal and TMUX is unset, so the same command in a boot script leaves the server running. --detach says so explicitly. The exit codes are ‘0’ restored, ‘2’ bad arguments, ‘3’ refused because sessions are live, ‘4’ nothing to restore, and ‘1’ for anything else.

[--exclude names] [--daemon-too] [--dry-run]
Save every session, then stop the tmux server. --exclude is not "leave these alone": stopping the server takes every session with it either way, so a session left out here is one that does not come back, and the command says which before it acts. --daemon-too stops the tmux-companion daemon as well.
[--exclude names] [--keep-daemon] [--dry-run]
The same, and then bring the server back with what it had. It stops the daemon by default, which --keep-daemon turns off. The default is that way round because config.toml is read once when the daemon starts and held for its whole life, so a restart that left it running would hand back a new binary, a freshly sourced tmux.conf, and yesterday's configuration.

Both refuse to run from inside tmux, because stopping the server would take the pane they were typed into and nothing after that would run. There is no flag for it. Both say so on standard error, as they do when no tmux server is running, and a sessions restart with no server to restart points at sessions resurrect instead.

[--once] [--status]
The snapshot timer the daemon runs, which [sessions] autosave turns on. --once takes one now and --status says when the last one happened, what the timer is set to, and whether the daemon before this one stopped cleanly. With neither flag it reports, the same as --status.

That last answer comes from a file the daemon writes when it starts and removes when it stops cleanly; the next daemon to start finds one naming a pid that is no longer alive and sets it aside as crashed, which is what the answer reads, so a daemon that is alive right now never counts as a crash. The marker stays until a restore has gone ahead on it, or a later crash replaces it. A snapshot's own record cannot answer it: a capture taken on the timer says ‘taken while running’ because the daemon does not know yet, and nothing goes back to correct the one that turned out to be the last before a crash.

[--json]
Every generation, newest first, with how much each holds and whether the daemon that wrote it went on to shut down cleanly. The newest carries a ‘*’. A generation is stamped in UTC, so on a machine well away from it the stamp and the file's own modification time are hours apart and both are right.
[stamp] [--json]
What one generation holds, down to each pane's directory and command, defaulting to the newest. The command is marked with how sure the capture was of it: ‘Exact’ for what the pane was started with, ‘Guessed’ for what it happened to be running, and a shell for a pane sitting at a prompt.
[--days n] [--print]
Every live session with no client attached and nothing happening in it for longer than n days, three by default, most idle first: the name, the directory, how long it has sat and how many windows it holds. Activity is what tmux counts, output in any window included, so a detached session with a job still printing is not idle. Picking one runs project close on it, layout capture and all, so a session shut from here opens again from the project picker as it was. --print lists the rows as tab-separated columns and exits. Without --print, inside tmux, the line saying there is nothing to show also goes to the tmux message line, so a popup that closes does not take it with it. No such session is one line on stderr and exit 0, not an error.
Stop the tmux-companion daemon, and only that. tmux and its sessions are not touched, and the next client starts a new daemon. sessions shutdown is the one that stops tmux.
Stop the daemon and start a fresh one, which is how a change to config.toml takes effect: the file is read once at startup and held for the daemon's whole life. tmux is not touched; sessions restart is the one that restarts it. Exits non-zero when the new daemon does not answer, which is what a config it refuses looks like; the reason is in the daemon log.
[--all, --query text, --refresh, --print, --unused]
Every binding tmux knows, searchable, with what it runs in the preview. Enter runs the binding. It opens on the bindings whose note starts with ‘companion: ’, which is what the shipped configs write with -N; --all opens on everything, including a ‘prefix’ or ‘root’ binding with no note, which a plugin's often is; its command stands in for the note, --query opens on something else, --refresh rebuilds the list from tmux rather than using what the daemon holds, and --print lists the rows and exits. Without --print, inside tmux, the line saying there is nothing to show also goes to the tmux message line, so a popup that closes does not take it with it. When no binding carries the note there is nothing to show, and it says so on standard error and exits 0 rather than drawing an empty picker. Bound to ‘prefix’?.

--unused narrows the opening set to the bindings the usage log has never recorded a press for, under the title ‘[ Keys never used ]’. Never means since the log began, and the line under the list says how many bindings that is out of how many, and how many presses the log holds; with no log yet it says so and lists every binding, which is the truth on a fresh machine. With --print it writes one table key note line per binding to standard output and the same summary to standard error. It does not combine with --all: tmux's own bindings are nobody's to prune.

keys discover [--layer layer, --print]
Reads what tmux's root table binds and what the apps in its panes bind, and saves it for keys collide. No app is asked to do anything new. Each app is read by the best way that answers: a running instance through an interface it already has, then its own configuration or a headless start with it, then what [keys.app.name] claims declares. nvim is asked live over the socket every nvim serves, one call to () per mode and nothing else, and every nvim pane answers for itself; when none does, one ‘nvim --headless’ start with your config answers instead. Only keys tmux's root table could stand in front of are kept: Ctrl, Alt and the F keys. claude is read from its keybindings.json over a table of its default keys, fzf from FZF_DEFAULT_OPTS over fzf's defaults, vim from ‘:map’ in a silent start with your vimrc, nano from the ‘bind’ lines of your nanorc with no defaults, since nothing installed lists them, and zsh from ‘bindkey -L’ in an interactive start; zsh is reported and never routed, since it is the command in every idle pane. fzf-lua's fzf keys come back from a live nvim when fzf-lua is loaded. --layer reads only one of ‘tmux’, ‘nvim’, ‘vim’, ‘claude’, ‘fzf’, ‘nano’ or ‘zsh’, and --print writes one layer mode key rung pane description source line per binding as well. What could not be read goes to standard error, one line per layer. It runs in the client; the daemon never starts or connects to an editor.
keys collide [--json, --refresh]
The keys tmux's root table binds that an app binds too, with every app that wants each one and the modes it wants it in, followed by the Ctrl, Alt and F keys nobody binds, for when a new shared action needs a home. Two spellings of one key, such as ‘M-S-a’ and ‘M-A’, count as one. A key the registry describes, with a field naming a layer and a word or two its binding should contain, is listed as drift when that layer binds it to something else or to nothing; collisions show the route the registry gives them. It reads the last keys discover, and discovers first when there is none or with --refresh. --json prints the report instead of opening the picker.
keys route [--dry-run, --print]
Wraps tmux's root bindings so that a key the app in front of a pane wants is held rather than taken. It belongs on the last line of tmux.conf, ‘run-shell "tmux-companion keys route"’, because it reads the root table as tmux.conf left it. Each Ctrl, Alt or F binding in root moves, untouched and with its note, into the ‘kc-tmux’ table, and root gets a wrapper carrying the same note. When nothing claims the key it runs the binding at once. When something does, it waits [keys] hold_ms, 170 by default: a second press in that time sends one key to the app, and when the time runs out, or another key comes first, tmux runs its binding and the other key follows. A claim is the pane-option contract below, or a static claim: what keys discover found for the app in front, in the modes that count, plus [keys.app.name] claims. A key whose name has punctuation in it is left alone. [keys.key.key] route settles one key: ‘hold’, the default, ‘app’, which hands a claimed key to the app at once and runs tmux's binding at once otherwise, the way vim-tmux-navigator shares its keys, or ‘tmux’, which leaves the key unwrapped; its hold_ms overrides the one in [keys]. With [keys] route off, the default, it puts every wrapped binding back and does nothing else; below tmux 3.4 it says so and wraps nothing. Running it again after tmux.conf is sourced wraps the fresh bindings. It prints nothing unless something fails; --print says what was wrapped and what was left alone, and --dry-run prints the tmux script as well and applies nothing.
keys claim [--app name] [--owner] [-t pane] [key ...]
Sets the pane options a wrapper reads, spelled the way it matches them, for a hook or a wrapper script. The keys go into ‘@kc_claim’; --app sets ‘@kc_app’, for an app whose process name doesn't say what it is; --owner records the pane's current command as ‘@kc_owner’, so the claim stops counting once something else is in front. The pane is TMUX_PANE unless -t names one.
keys release [-t pane]
Unsets all three.

The contract, for anybody writing a publisher: code inside an app that keeps its claims current as its mode changes. ‘@kc_owner’ is the ‘pane_current_command’ tmux shows for the app; ‘@kc_claim’ is the keys claimed right now in tmux's spelling, each between pipes, with a pipe at both ends: ‘|M-a|M-1|C-q|’. A claim counts only while the pane's command equals ‘@kc_owner’, so one left behind by an app that crashed stops counting once the shell is back. While ‘@kc_owner’ names the app in front, its static claims don't apply: a publisher that claims nothing in this mode unsets ‘@kc_claim’ and means it.

[--print]
The bindings you keep looking up, so you learn them and stop. A pick in the keys picker is a binding looked up rather than pressed, and with [usage] enabled on the log records each one with its time. A binding looked up within the last [usage] learned_after_days, 14 by default, is still being learned: it goes at the top of its box with a ‘▸’ and its count, most lookups first. One with lookups but none that recent has been learned and leaves the sheet; the line under it says how many did. 0 days means nothing is ever learned. A pick logged before picks carried a time counts as learned until it is looked up again.

Three boxes hold the bindings noted ‘companion: ’, by the word after it: panes, windows, sessions and projects; copy mode, opening and searching; config, help and anything else. The ones never looked up follow the marked ones, alphabetical. The fourth box, tmux and plugins, holds bindings without that note, and only ones looked up recently: tmux's own show tmux's note, and one with no note shows its command. A default nobody looked up is never listed.

With the usage log off, every ‘companion: ’ binding is shown unranked, the fourth box is empty, and the line under the sheet says it learns only with the log on.

--print writes one line per entry instead of drawing the sheet: box state count table key shown note, tab-separated, where box is 1 to 4 in reading order and state is ‘learn’ or ‘unused’. The line under the sheet goes to standard error. --plain is the old spelling and still works. Without --print, inside tmux, the line saying there is nothing to show also goes to the tmux message line, so a popup that closes does not take it with it. The same hint as keys when no binding carries the note. Bound to ‘prefix C-c’.

busy | | [--pane id]
What an agent is doing, said by the agent itself. Run from one of its hooks, it tells the daemon that the agent in $TMUX_PANE is working (busy): a prompt was sent or a tool just ran , has stopped on a question or a permission prompt (asked), or has finished its answer and waits for the next prompt (done). The last word per pane wins over the window's quiet time everywhere the daemon reads it: the bar's agents segment, the inbox, the panes picker and the brief, and it lasts as long as the pane runs one of [agents] programs. Nothing is printed and the exit is 0 whatever happens, since the command runs inside the agent's own hook and an error there would land in its transcript; outside tmux it does nothing. --pane names another pane.
agent clear [--pane id]
Forget what the pane last said, so the window's quiet time decides again: for an agent restarted without its hooks in a pane that still carries the old one's last word.
agent hooks [program]
Print the hooks block for an agent's settings file, the way shell-init prints the prompt marks. For ‘claude’, the only program so far, that is ‘UserPromptSubmit’ and ‘PostToolUse’ saying busy, ‘Stop’ saying done, and ‘Notification’ on a permission prompt or ‘PreToolUse’ on ‘AskUserQuestion’ saying asked, and ‘SessionStart’ and ‘SessionEnd’ running keys claim --app claude --owner and keys release, so key routing knows a pane whose process is named for claude's version is claude; all of it to merge into ~/.claude/settings.json. ‘idle_prompt’ is left out on purpose: it fires a minute after every answer and would turn each done into a question. Between a hook and silence sits the terminal bell: an agent that rings it sets the window's bell flag until the window is visited, and an agent pane with the flag up reads as asked.
[--print]
The agents waiting on you, longest wait first, each with the question it asked: the daemon captures the last lines of an agent's screen the moment it stops drawing, or the moment its hook says asked or done, so the question is on record for a window nobody has looked at since. The third column says how it stopped and for how long: ‘asked 3m’ for a hook or the bell, ‘done 3m’ for a hook that said the answer was complete, which is on the list to be read rather than answered and is not coloured, and ‘waiting 3m’ for silence alone. Enter switches the attached client to that pane. --print writes where, program, state and how long, the question and the pane id as tab-separated columns. Without --print, inside tmux, the line saying there is nothing to show also goes to the tmux message line, so a popup that closes does not take it with it. The question is the last line of the screen that is something the agent said: the input box at the foot of the screen and whatever is under it are cut, and then every line matching one of [agents] question_skip, a list of regular expressions that ships covering rules, spinners, hints and a dialog's numbered options. [agents] inbox turns the capture off, and [agents] nudge_after_secs says it out loud once an agent has waited that long, unless it said done, which is an answer and not a question.
[--agents, --print, -t session]
Every pane on the server, one row each: where it is as ‘session:window.pane’, what it runs, whether it has gone quiet, and its directory, with the last lines of its screen in the preview. Enter switches the attached client to that pane, whatever session it is in. The pane this runs in is left out, since jumping to where you are is nothing. The state column reads ‘busy’, ‘asked 3m’, ‘done 3m’ or ‘waiting 3m’ for a pane running one of [agents] programs, the first three when the agent said so through agent or rang the bell and the last when only the window's quiet time is known, ‘active’ or ‘idle 3m’ for anything else, and ‘reading’ for a pane in copy mode. Quiet is measured on the window rather than the pane, because tmux keeps no activity time per pane: an agent beside a shell you are typing in reads as busy. The program column carries the pane's title after a dash when a program set one, which is how two ‘claude’ panes in one directory are told apart. --agents keeps only the agent rows, -t only one session's, and --print writes the rows as tab-separated columns with the pane id last, and exits. Without --print, inside tmux, the line saying there is nothing to show also goes to the tmux message line, so a popup that closes does not take it with it. With nothing to show it says so on standard error and exits 0. Bound to ‘prefix g’, and ‘prefix G’ with --agents.
[--pane id, --client name]
Go to any text on the screen by typing a few of its characters and then the label that appears beside it, the motion flash.nvim gives neovim, over every pane in the window. A borderless popup is laid exactly over the window and shows what each pane shows, greyed. As the search is typed, its matches light up in the theme's colour, nearest the cursor first, and each gets a letter on the cell after it. No label is ever a letter that would continue a match, so typing on narrows the search and typing a label jumps, and a match keeps its letter while the search narrows. The search ignores case until it holds a capital. Enter takes the nearest match; escape, or backspace on an empty search, closes it. The jump selects the pane, puts it in copy mode and moves the cursor to the match by row and column, so a selection already started extends to it. A pane already in copy mode is searched where its view is scrolled to. --pane is the pane the key was pressed in and --client the client to open the popup on, both from the binding. It replaces tmux-jump, which took one character and, on tmux 3.5 and later, landed a column further right for every line above its target. Bound to ‘prefix Space’, and to ‘s’ in copy mode.
Every line of every pane's scrollback, one row each and the newest first: where it is as ‘session:window.pane’, what the pane runs, and the line, with the five lines on either side of it in the preview. Enter switches the attached client to that pane, puts it in copy mode scrolled to the line and selects the line, so what you found is what's highlighted and the copy key takes it. The panes are read most recently active first, a blank line is no row, and a line one pane drew twice is one row at its newest, so a prompt drawn forty times doesn't fill the list. pattern keeps only the lines it matches. It's a regular expression, matched in either case unless it holds a capital letter, and -F takes it as text, for an error message full of brackets. --kind is a stored search in place of a pattern: url, path, sha or ip. -t keeps one session's panes and --pane one pane. --lines is how far back each pane is read, 1000 when nothing says, and the list stops at 20000 rows. --print writes the rows as tab-separated columns and exits: where, the pane id, the line's number as tmux counts it, with 0 the top row of the screen and the history below zero, and the line. Without --print, inside tmux, the line saying there is nothing to show also goes to the tmux message line, so a popup that closes does not take it with it. --first goes to the newest match without showing the list, and needs a pattern or a kind. A line is a line the terminal drew, so a URL that wrapped is two of them and neither matches whole. What a pane wrote while the list was open moved the line up, so at landing the pane is read again and the line found by its text, which also holds for a pane whose history is already at ‘history-limit’ and can't grow. The stored searches come from tmux-copycat, which hasn't been pushed since May 2023, and the fuzzy list over one pane is tmux-fuzzback, which is the one to install on a machine that doesn't run this. Bound to ‘prefix /’.
[--all, --udp, --print, -t session, --kill, --grace secs]
The TCP ports something is listening on, one row each: the port and its protocol, where it can be reached from, the program and its pid, the pane that started it as ‘session:window.pane’, and that pane's directory, with the last lines of the pane's screen in the preview. Enter switches the attached client to the pane. The address column reads ‘*’ for every interface, ‘localhost’ for either loopback, or the address it is bound to, and a program listening on one port over IPv4 and IPv6 is one row. A port belongs to the pane whose own process is an ancestor of the one listening, so a server started by a script started by an agent is still that agent's pane's. Only the ports a pane started are listed; --all adds the rest after them, with a dash for the pane. --udp adds the UDP sockets that are bound and connected to nobody, which is as near as UDP comes to listening; it's asked for rather than given because any program that resolves a name holds one for a moment. -t keeps one session's, and --print writes the rows as tab-separated columns and exits: port, protocol, address, program, pid, where, and the pane id. Without --print, inside tmux, the line saying there is nothing to show also goes to the tmux message line, so a popup that closes does not take it with it. With --kill enter stops the program where it would have gone to the pane: TERM to the process holding the socket, and KILL if it's still there after --grace seconds, 3 when nothing says. Only that process is signalled and not its group, since what started it is often the pane's shell. The sockets come from lsof(8), or from ss(8) on a machine without it, and either one names only the processes you own unless you're root. Bound to ‘prefix P’.
[--print]
A window here, or at any directory. The rows are the pane's own directory and then the same source project lists, so it is the one setting for both. The query starts on the pane's own directory, which is also the first row, so the key and enter is another window here. A path the source has never seen opens when it is typed in full. --print writes the rows and exits: a column that says ‘here’ on the pane's own directory, the short path and the full one. Bound to ‘prefix c’.
[--pane id]
A command from shell history, run in a pane that slides out beside this one and waits for an answer before closing. --pane names the pane the side pane belongs beside. The shipped binding passes nothing, and then the session an attached client is looking at decides. Both exist because the picker draws inside display-popup, which is not a client: an untargeted split is resolved against whichever session the server touched last, so the command ran in a window nobody was looking at. tmux does not expand ‘#{pane_id}’ in a display-popup command at all, and expands it in a run-shell one to the pane the server touched last rather than the pane the key was pressed in, which is why the flag is for a caller that knows the pane and not for the binding. Bound to ‘prefix e’.
[--themes dir]
Every theme with its colour beside it, applied on the spot and remembered against the project. -t applies it somewhere other than where you are; with nothing given it is the session an attached client is looking at, because the picker is a popup and a popup is not a client, so tmux's own idea of the current session is the one it touched last. Bound to ‘prefix C-t’.
[text] [--pane id, --clear]
Leave a one-line note on a pane: tmux's pane title, which panes shows beside the program and the pane border draws when pane-border-status is on. With no text it prints the note that is there; --clear takes it off. A snapshot carries the note and a restore puts it back. The pane defaults to the one the key was pressed in. An agent working beside you can say what it is doing in four words with this, and the skill file tells it to.
[duration] [--off]
Quiet hours. For the duration given, ‘45m’, ‘2h’ or a bare number of minutes, [notify] announces nothing, the inbox nudges nobody, and the agents segment leaves the bar; the health mark says ‘quiet’ in its place, so the silence reads as chosen rather than broken. With nothing given it says how long is left; --off ends it now. [quiet] daily turns it on at the same local times every day, such as ‘22:00-08:00’; a timer and a window together last until the later one ends. The daemon keeps the clock, so every timer and every client agree.
[status [--json] | snooze | reset | close | budget duration]
The chunk clock: how long you have been at tmux this sitting, across every session, with nothing to start. Time counts while an attached client has the OS focus, which needs focus-events on, and saw a key or click within [chunk] break_after; a shorter gap counts as work, and one that reaches it is a break that ends the sitting. The chunk segment of [[status.right.segments]] draws a timer and ‘38m’, amber past [chunk] warn_at of the budget, and an alert timer and ‘50m+12’ in red past it, with ‘• 1h14m’ after either when the pane in front runs an agent, how long it has been running, unless [chunk] agent_age is off. At budget the daemon plays the ‘chunk’ earcon once, at the next window switch, prompt coming back or agent finishing its turn, and at most [chunk] boundary_grace late; quiet hours keep the sound and drop the [chunk] banner. status, the default, says the sitting in words, ‘38 min, 12 min for break’ or ‘1 hr 7 min, 17 min passed break time’, and nothing when no sitting runs; brief says the same line. snooze, past the budget, grants one extension of [chunk] snooze and one more sound, once a sitting. reset starts a new sitting at 0, close ends this one until you have been away and back, and budget sets this sitting's budget until a reset or a daemon restart. The sitting survives a daemon restart in $XDG_STATE_HOME/tmux-companion/chunk.toml. A terminal that does not report focus reads as focused forever, and there only the key counts.
[--print] [-t session] [--days n]
What happened in each project, newest first: a command that ran past [journal] min_secs and finished, with how long it took; an agent that stopped and what it asked; an agent that worked one turn past [journal] agent_min_secs and said it was done, with how long and the last line of its answer; a project opened or closed. Enter goes to that session. -t keeps one session and --days reaches further back than the last day. --print writes the rows as tab-separated columns. Without --print, inside tmux, the line saying there is nothing to show also goes to the tmux message line, so a popup that closes does not take it with it. The daemon writes it to journal.tsv in the state directory as it happens. It is off by default.
[session] [--last]
Move to the next window in this session, in index order, wrapping from the last to the first. Windows are told apart by index, so two windows with the same name are two stops. --last flips to the window the session was on before instead, which is tmux's own last-window: with two windows the two are the same key, and with four the flip is what a toggle means and the cycle is a tour. A window after the session is accepted and ignored, so older bindings that pass ‘#{window_name}’ keep working. Bound to ‘M-a’, and --last to ‘M-A’.
project close [session] [--discard, --no-save]
Close a project by asking every window to exit, capturing the layout on the way out. An editor is asked to quit first, in its own language, and the close stops with the editor on screen when it will not; the editors it knows are nvim, vim, vi and hx. Everything else, an editor it does not know included, gets ‘exit’ or ctrl-D. --discard quits editors with ‘:qa!’ and throws away unsaved work. A session that is not there is an error rather than a session that closed at once. Bound to ‘prefix X’.
[name] [--pane id]
A shell you pull out beside the pane you are in, look at, and put away without losing it. The first press splits a shell off the side of the window at [run] width_percent; the second parks it in a window called ‘_pocket’; the third brings the same pane back beside wherever you are now, its process and its scrollback as they were. name tells one pocket from another when a session keeps more than one, and defaults to ‘shell’. A pocket is known by the pane option ‘@tmux-companion-pocket’ rather than by its title, which a shell is free to change. toggle cycles past the ‘_pocket’ window, and project save leaves it, and any pocket that is out, off the layout it writes. --pane is the pane the key was pressed in, which the binding passes. Pressed inside the ‘_pocket’ window it does nothing and says so. Needs tmux 3.1 for pane options. Bound to ‘prefix `’.
[name] [--pane id]
Give a pane a session of its own, its process and its scrollback with it: for the pane opened beside the editor to look at another repository, which an hour later is the thing being worked on. The session is named the way project names one, from the pane's directory, so it is the session project finds the next time that directory is picked. name is for when the directory's name won't do. When a session of that name is already there the pane joins it as a window, and when the pane is all its own session holds the session is renamed and nothing moves. A pane already in the session it would be given is refused, with a line saying to pass a name. The attached client follows the pane, and the journal gets an ‘opened’ line for a session this made. --pane is the pane the key was pressed in, which the binding passes. The idea is tmux-sessionist's ‘prefix @’, which asked for the name, and it hasn't been pushed since May 2023. Bound to ‘prefix @’.
[--pane id, --grace secs, --ask]
Stop what runs in front in a pane, for a program that stopped answering ‘C-c’. The foreground process group of the pane's terminal gets TERM, which a program can catch to put the terminal back and remove its lock file, and KILL when it's still running after --grace seconds, 3 when nothing says. The group is the one the terminal delivers ‘C-c’ to, so a build goes together with the compilers it started and a job put in the background with ‘&’ is left alone. It prints how it ended, ‘cargo stopped on TERM’ or ‘cargo ignored TERM for 3s and was killed’, and exits 1 when the program is still there after KILL or could not be signalled. A pane with nothing in front but its own shell is refused, since stopping that closes the pane and tmux has ‘kill-pane’ for it. --pane is the pane the key was pressed in, which the binding passes. --ask looks before it asks: a shell at its prompt is refused with no question, and anything else gets tmux's own confirmation naming the program and its pid, whose yes runs the stop. tmux-cowboy was the one key for this, sending KILL with no TERM before it, and was last pushed in May 2021. Bound to ‘prefix K’ with --ask, so the confirmation names what it stops.
[--pane id]
Clear everything but the pane you are working in. With other panes open that is tmux's own zoom: this pane fills the window, the rest are hidden, and the same key puts the layout back untouched. With no other panes there is nothing to zoom -- a lone pane already fills the window, and tmux's own ‘prefix z’ does nothing at all there -- so the key takes the status bar instead, which is the only clutter left. One key for "give me the most screen you can", whatever the window looks like. zoom is the old name and works for one more release. Bound to ‘prefix z’.
[--once, --status]
Deprecated: the [autosave] script timer, which [sessions] autosave replaces. --once runs the script now, --status says when it last ran, and with no flag it reports, the same as --status.
[text] [-s, -i, -n, -d dir, --pane id, --cursor-x column]
Open what is in the text: a URL goes to a browser, a ‘path:line:column’ opens an editor there. --cursor-x names the column the cursor is in, and what sits under it wins. The shipped binding passes the line under the cursor together with ‘#{copy_cursor_x}’, so nothing has to be selected first: being on the thing is the whole gesture, and a line naming two paths opens the one the cursor is actually on rather than whichever came first. -s reads the tmux selection instead of arguments, for a binding that would rather select a range by hand. -i, or --choose, asks which application opens it, from the [[open.application]] list. With nothing in that list there is nothing to choose between, so it opens what it would have opened anyway rather than showing an empty picker. -n prints what it would open and opens nothing. --pane names the pane this is for, and the shipped binding passes ‘#{pane_id}’ so that it does. tmux runs the binding through run-shell, where TMUX_PANE holds the most recently active pane on the server rather than the pane the key was pressed in, so without it the editor can open in a window nobody is looking at. Bound to ‘o’ in copy mode.
Copy standard input to the system clipboard, whichever command this platform has. Bound to ‘y’ in copy mode.
shell
Print the shell code that emits the OSC 133 prompt marks. tmux has had next-prompt and previous-prompt since 3.3 and they do nothing until the shell says where a prompt begins.
eval "$(tmux-companion shell-init zsh)"     # or bash
tmux-companion shell-init fish | source
On its first prompt the shell sets the pane option ‘@tmux-companion-marks’ to 1, once, inside tmux only.

A theme is a file of tmux options that _apply.tmux turns into colours. Options are set without -g, so a theme belongs to one session and two projects can be different colours at once.

[--themes dir]
Write the starter themes and the two files that apply them. Six colours, which theme gen --shades grows to 151. Nothing is overwritten.
[--apply] [--shades] [--background colour]
Compute each theme's readable text colour and a visible border. Without --apply it reports what would change and touches nothing. --shades [level] writes a theme for every colour in the 6x6x6 cube whose text clears a contrast floor, named after the bundled colour each sits nearest to, so ‘ember-04’ and ‘pine-11’ land together in the picker. level is a rung of a ladder, and the flag on its own means ‘aaa’:
4.5:1 216
7:1 151
9.5:1 105
12:1 75
— 18

WCAG names the first two rungs and stops; the rest continue at its own spacing of 2.5 per step, so the ladder is one rule rather than four opinions. a6 is not a floor at all: it is the bundled six with a lighter and a darker sibling of each, which is what --shades did before it swept the cube, and the only rung whose colours a person chose. aaa is the default rather than aa because the worst colour in the cube scores 4.60:1, so an AA filter keeps all 216 of them (a threshold that is arithmetically a no-op). The text colour is chosen to clear 4.5:1 against the theme's own background and the border to clear 3:1 against the terminal's, so a theme that cannot be read cannot be written.

--bg colour [--fg colour]
A theme from one colour. The text colour is computed unless given, and a given pair under 4.5:1 is refused without --force.
[session] [-t target, --all]
The theme a session should have, without asking. Run from the session-created hook. Silent when there is no theme to apply, which is every install until theme init has run. With [theme] ‘default = by-name’ a session nothing claims gets one of the six bundled themes chosen from its name, the same one every time, so every session has a colour of its own with nothing picked. --all repaints every session instead of one, which is what a reload needs: sourcing tmux.conf resets the global options a theme sets, so without it every session is left wearing whatever the file says rather than its own colour.
[--print]
Every colour tmux takes, painted, with its hex and the contrast on it. --print is one name per line with no swatch, for piping somewhere; --plain is the old spelling and still works.

A short sound for something a sighted person would see on the bar, for somebody who hears the screen rather than glancing at it.

event
Play the sound event makes, ‘asked’, ‘done’, ‘health’ or ‘chunk’, now, whether sounds are on or not, and say which command it ran. With [earcons] enabled, the daemon plays them itself: once per pass when agents start waiting on you or finish, and when a health reason appears that was not there before, checked every ten seconds whether or not a bar draws the health mark. [earcons] on says which events sound, and asked, done, health and chunk the command each runs, the platform's own sound when empty. Quiet hours hold them; a health reason that appeared during them sounds when they end.

TOML, read from the first of:

  • $XDG_CONFIG_HOME/tmux-companion/config.toml
  • ~/.config/tmux-companion/config.toml
  • ~/.tmux-companion.toml

Every setting with its default, annotated, is docs/config.example.toml. The sections are [general], [dirs], [git], [network], [battery], [glyphs], [status], [sh_jobs], [usage], [[layout]], [autoreload], [window_names], [notify], [agents], [project], [autosave], [sessions], [[restore.program]], [run], [clipboard], [theme], [bar], [picker], [journal], [online], [open], [earcons], [chunk] and [keys].

Which file is being read.
Parse it and say what is wrong, with the line and a suggestion for a misspelled key or an unrecognised value. It also names any setting that still works but is deprecated.
Every setting with its default, as a config file.
[--force]
Write a short starter config where config path would read it, creating the directory, and print the path. The file sets the glyph preset, the directory source and the snapshot timer, and carries the editor-beside-an-agent layout as a comment to uncomment; the rest is left to the defaults, which config dump prints in full. A file already there is refused unless --force.

Two settings are worth knowing before the rest. [bar] background is what the segments draw their caps against and has to match status-style; it used to be a compiled-in ‘colour233’, which is one person's bar and nobody else's. [picker] preview is where every picker puts its preview pane, or ‘none’ for no preview at all. The rest of that section is what a picker looks like rather than what it holds: border, label_position, hint_position, which the brief, the restore screen, the run dialog and the cheat sheet follow too, so ‘hidden’ takes the line of keys off every popup while a restore counting down still says when it goes ahead, hint_across, which makes that line a row across the whole popup over the list and the preview both, prompt_position, list_from, counter, rules, marker, column_order and the preview's own label. Each of them can be written again under [picker.keys], [picker.project], [picker.window], [picker.theme], [picker.run], [picker.open], [picker.panes] or [picker.setup] for the one picker that wants a different answer, together with the label, hint and preview_label that picker says. How big the popup itself is belongs to display-popup in tmux.conf, not here. [agents] programs is the one list of what counts as a coding agent, matched against ‘pane_current_command’; a command that is only a version number, ‘2.1.283’, counts as well, since that is what tmux reports for claude, which names its process after its version, and nothing else names a process that way. The list is read by panes --agents, the agents segment and the restore headline alike; waiting_secs is how long one has to be quiet before it counts as waiting on you, when it has not said better through agent, interval_secs how often the bar's segment re-reads the pane list, and show whether the count after the total is the agents waiting, the agents busy, or both. [open] split is which side the editor opens on, ‘right’ or ‘bottom’.

[project] trusted is the paths under which a checkout's .tmux-companion.toml may run what it says; see FILES. [project] dirs_source is where the project picker gets its directories, one of ‘zoxide’, ‘z’, ‘cdr’, ‘ghq’ or ‘none’. zoxide(1) is the default rather than a requirement, and nothing here has to be installed: a source that is absent lists nothing, leaving live sessions and whatever is typed. ‘z’ reads the ~/.z database and ‘cdr’ reads ~/.chpwd-recent-dirs, as files rather than as commands, because both are shell functions that cannot be run from here. [project] dirs_command takes any command printing one absolute path per line, as a list of words, and wins over dirs_source; [project] visit_command is what records a pick, defaulting to ‘zoxide add’ for zoxide and nothing for the rest. [project] zoxide is the old spelling of ‘dirs_source = "none"’ and goes away in the next release.

[[git.branch_types]] is the glyph drawn before a branch name, keyed on how the name starts. Each entry is an icon and a list of prefixes, tried in the order written; the first prefix that claims the name wins, and it is cut off the name the bar draws, so ‘feat/status-bar’ renders as the feature glyph and ‘status-bar’. Prefixes are compared without regard to case and are plain text rather than regular expressions. icon takes ‘{NAME}’ for any glyph the tool knows, so the file stays readable without a patched font, and draws anything else as written. The list replaces the six shipped entries rather than adding to them; config dump prints them in the shape they are edited in. A name no entry claims keeps the plain branch glyph, which is why ‘main’ and ‘master’ need no entry.

What a bug report needs: the version, the socket, what the config says, the last line of the daemon log, whether either autosave is running and when it last did, every reason the health mark would show, what tmux says, and what is missing. A daemon from another build, one that does not answer, and a config file edited after the daemon started each end their line with ‘run tmux-companion restart’, because that is the answer to all three. The report ends on tmux's own options, the ones that cost something as they are set: an ‘escape-time’ over 50 ms, a ‘history-limit’ of 2000 lines or under, a ‘display-time’ under two seconds, a ‘status-interval’ over five or of zero, ‘focus-events’, ‘set-clipboard’, ‘monitor-bell’ or ‘mouse’ turned off, and a ‘default-terminal’ with no 256 colours in its name. Each gets the value it has, what that costs, and the line for tmux.conf under it. Nothing is set: the file is yours, and ‘mouse’ for one is a preference. A tmux with nothing to change says so in one line. Most people got these settings from tmux-sensible, which set them as a plugin and hasn't been pushed since April 2024. The ‘setup’ line counts the items setup lists as open.
[ack]
Why the health mark is up, one reason a line, or ‘ok’. A timer or a segment that failed stays a reason for an hour, until the same timer runs and works, or until health ack forgets it, whichever comes first; ack prints each failure it forgot. It forgets failures and nothing else. A config.toml edited after the daemon started, a newer binary on disk, quiet hours and a network that's gone are how things are, so they stay, and ack lists them under what it forgot as ‘still on the mark’. The sessions timer finding no tmux server, or a server with no session in it, isn't a failure and is never a reason: every session closed is something people do, and the daemon is still there when tmux comes back.
What the terminal answers about itself.
~/.config/tmux-companion/config.toml
the configuration
.tmux-companion.toml
in a project root, the layout that checkout opens with: [[window]] rows in the shape of [[layout.window]], or layout naming one of the reader's own; its commands run only under a path [project] trusted lists, and elsewhere the names alone are taken. config check run inside the checkout reads it too
~/.config/tmux/themes/
themes, and the two files that apply them
$XDG_STATE_HOME/tmux-companion/projects/
saved per-project layouts
$XDG_CONFIG_HOME/tmux-companion/keys.toml
the [keys] settings and tables, without the ‘keys.’ prefix, beside the config file; both files count, and a name set in both is an error
$XDG_STATE_HOME/tmux-companion/keys-discovered.json
what keys discover found in each layer, read by keys collide
$XDG_STATE_HOME/tmux-companion/keys-route.tmux
the script the last keys route sourced, kept for reading when a wrapper misbehaves
$XDG_STATE_HOME/tmux-companion/keys-usage.tsv
which bindings the key picker ran, written only with [usage] enabled on: one table key @secs line per pick, the time in unix seconds, until it passes five thousand lines, then one table key count first last line per binding under a ‘#v2’ header. A log written before picks had a time, raw or under ‘#v1’, is still read
$XDG_STATE_HOME/tmux-companion/VERSION
the build that last started a daemon here, rewritten on every start
$XDG_STATE_HOME/tmux-companion/sessions/running
the pid of the running daemon, written at start and removed by a clean stop, which includes SIGTERM and SIGINT
$XDG_STATE_HOME/tmux-companion/sessions/crashed
the running file of a daemon that never removed it, moved here by the next daemon to start once it has found that pid dead; its presence is what makes sessions resurrect treat the last snapshot as taken before a crash
$XDG_STATE_HOME/tmux-companion/setup-skipped.tsv
the ids setup was told to skip, one per line
$XDG_STATE_HOME/tmux-companion/build-seen
the build a daemon first ran here and when, tab-separated
$XDG_STATE_HOME/tmux-companion/journal.tsv
what ran long, what the agents asked, what opened and closed
$XDG_STATE_HOME/tmux-companion/daemon.log
the daemon's stderr: one line per start, one per failure, unless [general] log points elsewhere
/tmp/tmux-companion-$UID.sock
the daemon's socket
/tmp/tmux-companion-$UID.sock.lock
the lock one daemon holds for the life of the process, so a second start cannot unlink the socket out from under the first
~/.z
the z database, read when [project] dirs_source is ‘z’
~/.chpwd-recent-dirs
zsh's recent directories, read when [project] dirs_source is ‘cdr’
a configuration file, before the search order
the daemon socket, before the default. A client replaces a daemon from an older build and leaves a newer one alone, saying so once, so a development build run beside the installed one wants a socket of its own here rather than the two taking turns replacing each other's daemon
the tmux configuration the daemon watches for key bindings and [autoreload], before the first of $XDG_CONFIG_HOME/tmux/tmux.conf and ~/.tmux.conf that exists
, XDG_STATE_HOME
where configuration and state live
the z database, before ~/.z
where zsh keeps .chpwd-recent-dirs, before the home directory

The tmux-companion utility exits 0 on success, and >0 if an error occurs.

A binding that runs from run-shell prints its complaint on standard output rather than exiting non-zero, because tmux turns a non-zero exit into ‘'tmux-companion open -s' returned 1’ in the message area, which names the command and not the problem.

tmux(1), zoxide(1)

https://github.com/lonkar-org/tmux-companion

Yogesh Lonkar

September 26, 2026 Debian