When you split a window into several panes — say a dev/planner/reviewer layout — it's easy to lose track of which directory each one is actually in. tmux exposes pane_current_path, so you can paint the cwd right onto the pane's top border:
set -g pane-border-status top
set -g pane-border-format " #{pane_index}: #{pane_title} [#{pane_current_path}] "
Full paths get long and crowd out the title. Two ways to trim. #{b:...} gives just the basename (last component):
set -g pane-border-format " #{pane_index}: #{pane_title} [#{b:pane_current_path}] "
Or abbreviate $HOME to ~ with tmux's s/search/replace/ modifier. The neat trick is that the replacement is itself a format, so you can nest #{HOME} inside it — no hardcoded username, so the same dotfile works on every machine:
set -g pane-border-format " #{pane_index}: #{pane_title} [#{s|^#{HOME}|~|:pane_current_path}] "
s takes any delimiter after it; using | keeps the slashes in the path readable, and ^ anchors the match to the start so a directory that merely contains your home path elsewhere isn't touched. A pane in /home/you/Projects/foo now shows [~/Projects/foo].
tmux doesn't watch the config file — after editing, reload the running server with tmux source-file ~/.tmux.conf (or prefix then :source-file ~/.tmux.conf). Without that, only a fresh server start picks up the change.
One caveat worth knowing: pane_current_path is the cwd of the pane's foreground process, and tmux only updates it on chdir(2). A shell reflects it faithfully. But a long-running process that was started in one directory and then operates on files by absolute path — without ever cd-ing — won't move the border, because its cwd never changed. So the border shows the shell's real cwd, which isn't always where a process is "logically" working.
A common pattern for "hop to another session before killing this one" looks like this:
bind Q run-shell 'next=$(tmux list-sessions -F "#{session_name}" 2>/dev/null \
| grep -v "^#{session_name}$" | head -1); \
if [ -n "$next" ]; then tmux switch-client -t "$next"; \
tmux kill-session -t "#{session_name}"; \
else tmux kill-session; fi'
It doesn't work. run-shell expands all #{...} format strings before handing the command to the shell. So list-sessions -F "#{session_name}" becomes list-sessions -F "mysession" — a literal string, not a format specifier. Every session prints "mysession", grep -v strips them all, $next is always empty, and you drop straight to bash.
The fix is one line:
set -g detach-on-destroy off
bind Q kill-session
detach-on-destroy off is a native tmux option: when a session is destroyed, the client automatically switches to another surviving session. It only falls back to exit when there's nothing left. No shell escaping, no format-string footguns, and it covers every exit path — not just prefix+Q.
The standard way to switch to the previous tmux session is prefix + L (or a custom prefix + b). The problem: every switch requires the full prefix chord again. You can't hold Ctrl and keep tapping — tmux swallows the first keypress as the prefix and expects a fresh command key each time.
Use a no-prefix binding instead. Add to ~/.tmux.conf:
# Alt+b to switch to previous session (no prefix needed).
bind -n M-b switch-client -p
The -n flag means "no prefix required." Now you hold Alt and tap b repeatedly to cycle through sessions — no prefix dance, no finger gymnastics.
Why Alt+B and not something else: it's close to the default prefix (Ctrl+B) so it's easy to remember, and it doesn't conflict with any common terminal or shell binding. Your mileage may vary if you use Alt-heavy terminal apps.
Tools like team and adhoc (from the mux-relay framework) derive the project root from $PWD at invocation time. The natural instinct is to open a new tmux window, cd to the target directory, then run the command. That works, but it leaves a ghost window in the original session — and if that window gets cleaned up or the session reshuffles, pane working directories can silently drift to deleted paths.
A subshell avoids all of that:
(j my-project && team)
The parentheses spawn a subshell. Inside it, j (zoxide) changes directory to the project, then team launches the new session with the correct $PWD. Because new-session -d is detached and switch-client handles the jump, the new session is fully independent — no nesting, no leftover windows.
When the subshell exits (right after team returns), the original pane's $PWD is untouched. You never left it.
This pattern works for any command that reads $PWD but doesn't accept a -C / --directory flag:
(cd ~/Projects/something && explore)
(cd $(z resolve another-project) && adhoc claude)
One pane, one command, zero side effects.
When you're staring at a 5000-line diff, your eyes glaze over before you've read past the first file. diffstat gives you the bird's-eye view in one screen:
gh pr diff 137 | diffstat
lib/relay-runtime.sh | 2692 -----------------------------------
lib/relay/backends.sh | 142 ++++
lib/relay/cli.sh | 326 +++++++
lib/relay/cycle.sh | 99 ++
lib/relay/issue-comments..| 179 ++++++
lib/relay/issues.sh | 173 ++++++
lib/relay/project-regist..| 274 ++++++++
lib/relay/session.sh | 692 ++++++++++++++++++++++++++
lib/relay/telegram.sh | 389 ++++++++++++++++
lib/relay/tmux-send.sh | 337 +++++++++++++++
lib/relay/worktree.sh | 206 ++++++++
11 files changed, 2848 insertions(+), 2686 deletions(-)
From that one output you can immediately see: this is a file split (one huge file → ten smaller ones), the net change is roughly neutral, and session.sh is the biggest new file at 692 lines. That's enough to form a review strategy before reading a single line of diff.
It also works with local diffs:
git diff main..my-branch | diffstat
Install on Debian/Ubuntu:
sudo apt install diffstat