I wanted to share a quick thing that made my life easier on tmux lately, but before we dig into that, I feel like I need to explain how I usually work.

How my workflow looks like

It has 2 main modes:

Mode 1: Local

I open an Alacritty windows and run:

$ tmux

Mode 2: Remote

This is the mode I use the most: I open an Alacritty window and run:

$ ssh darkstar

This will SSH into my home server, directly into a tmux session. See the config bellow to better understand it:

Host darkstar
	HostName darkstar.local
	ForwardAgent true
	RequestTTY yes
	RemoteCommand tmux new -A -s default

On both modes, I then keep that single terminal window and work with tmux sessions, windows and panes from there — usually, one session for each project I’m working on, using tmux-sessionizer.


First and foremost, what I’ll show you is heavily based on previous work by ThePrimeagen, so, big shout out to him! Also check out his Twitch, it is amazing!



tmux-sessionizer is a binary available in my $PATH, and I have the following line in my tmux.conf:

bind-key -r f run-shell "tmux neww tmux-sessionizer"


When I press CTRL+b f I get a fzf window with my projects. From there I can filter the results, either creating and switching to a new session or just switching to an existing session.

Main difference from Prime’s version is that mine hooks into zoxide, sorting by most used.

Here’s a commented out version of the main changed line:

	# get my project's namespaces
	# I have it organized in namespaces per organization, so I have
	# my personal org (caarlos0), one for work, one for goreleaser, and so on.
	find $PROJECTS/* -type d -maxdepth 0 |
	while read -r p; do
		# get the projects in each namespace with their zoxide scores
		zoxide query -l -s "$p/";
	done |
	sort -rnk1 |     # sort by zoxide score (first column)
	fzf --no-sort |  # pipe to fzf without its sorting
	awk '{print $2}' # use the path as selected (second column)


The main advantage to this: it is predictable. I don’t even know how many sessions I have open, nor what session has what running… all I know is that I create one session for each project I work on and that I always have a default session to work on random things.

I also always get the most used projects first, so that prevents some keypresses, which is always nice.

A wild problem appears

I have been closing terminals with CTRL+d for as long as I can remember, so that’s burned in my “muscle memory” forever.

The issue with my workflow is that, when I’m in the last pane of the last window of the current session, if I exit that shell, it also exits tmux (because my SSH command is tmux), and, as expected, it also kills my SSH connection.

When that happens, I sight and reconnect.

Eventually, I had enough and decided to look for a way to fix it. After spending way too much time thinking how I could override CTRL+d in tmux, I thought of a simpler alternative: a shell trap!

Shell traps

Traps allow you to listen and to POSIX signals and do things when they happen.

In our case, we want to:

  • check if the shell is not interactive, if it isn’t, we do nothing
  • check how many panes and windows the current session have, if it is more than 1, do nothing
  • if its exactly 1 (i.e. last pane of the last window of the current session), switch to the default session before this one dies

I use the Fish shell, so I can just create a ~/.config/fish/conf.d/tmux-trap.fish file with the following contents:

function __trap_exit_tmux
	test (tmux list-windows | wc -l) = 1 || exit
	test (tmux list-panes | wc -l) = 1 || exit
	tmux switch-client -t default

if status --is-interactive
	trap __trap_exit_tmux EXIT

This will do exactly that! Permalink.

When I press CTRL+d in the last window, it’ll switch to my default session, and the EXIT signal will proceed killing that session. Works like a charm.

Here’s a quick video demonstrating it (pardon my voice/slowness, I have a strong cold):

So there you have it! Hope you find this useful!

Here are the links to tools and people mentioned in this post: