tmux Setup Guide

MuxPad renders your server's tmux exactly as-is over SSH — 100% raw tmux, so your prefix, key bindings, and status bar all stay. Because of that, the remote server must have tmux installed. This guide walks through installing tmux, tuning a couple of optional ~/.tmux.conf settings, and making your first connection.

1. Requirements

2. Install tmux

Install tmux with your server's package manager, then confirm the version.

# Ubuntu / Debian
sudo apt update && sudo apt install -y tmux

# Fedora
sudo dnf install -y tmux

# RHEL / CentOS
sudo yum install -y tmux

# Arch
sudo pacman -S tmux

# Alpine
sudo apk add tmux

# openSUSE
sudo zypper install -y tmux

# macOS (Homebrew)
brew install tmux

# FreeBSD
pkg install tmux

Verify the install:

tmux -V

2-b. Or let an AI do it — one prompt

Already running Claude Code (or another AI CLI) on your server? Paste this single prompt and it will install tmux, apply the recommended settings, and wire up Claude Code notifications for MuxPad — everything in sections 2–4:

You are setting up a server for MuxPad (an iPad/iPhone tmux terminal client). Do all of the following, idempotently and without breaking my existing setup:

1. Install tmux if it is not installed (use this system's package manager). Verify with `tmux -V` (3.2+ preferred).
2. Ensure these lines exist in ~/.tmux.conf (append only if missing):
   set -g mouse on
   set -g set-clipboard on
   set -g allow-passthrough all
   set -g history-limit 50000
   set -g allow-rename off
   set -g automatic-rename off
   If a tmux server is running, apply them with `tmux source-file ~/.tmux.conf`.
3. Create ~/.claude/hooks/muxpad-notify.sh (chmod +x) with exactly:
   #!/bin/sh
   [ -n "$TMUX_PANE" ] || exit 0
   title="${1:-Claude Code}"; body="${2:-Needs your attention}"
   printf '\ePtmux;\e\e]777;notify;%s;%s\a\e\\' "$title" "$body" \
     > "$(tmux display -p -t "$TMUX_PANE" '#{pane_tty}')"
4. Merge into ~/.claude/settings.json (back it up first; preserve all existing hooks and settings):
   - hooks.Notification += run the script with body "Waiting for your input"
   - hooks.Stop += run the script with body "Task finished"
5. (Optional — Claude status badge+message: sidebar shows running=prompt prefix, needs_input=what Claude asks, idle=completion phrase. Requires python3) Create ~/.claude/hooks/muxpad-status.sh (chmod +x) with exactly:
   #!/bin/sh
   [ -n "$TMUX_PANE" ] || exit 0
   s="${1:-running}"; m=""
   if [ "$s" = auto ]; then
     cur=$(tmux show-options -p -t "$TMUX_PANE" -v @claude_status 2>/dev/null)
     case "$cur" in running|idle) exit 0 ;; esac
     s=running
   fi
   j(){ python3 -c 'import sys,json;print(" ".join(str(json.load(sys.stdin).get(sys.argv[1],"") or "").split())[:120])' "$1" 2>/dev/null; }
   case "$s" in
     running) m=$(j prompt) ;;
     needs_input)
       m=$(j message)
       case "$m" in *"waiting for your input"*) exit 0 ;; esac
       ;;
     idle) m="${2:-🎯 Done}" ;;
   esac
   t=$(tmux display -p -t "$TMUX_PANE" '#{pane_tty}')
   if [ "$s" = off ]; then
     tmux set-option -p -t "$TMUX_PANE" -u @claude_status 2>/dev/null
     tmux set-option -p -t "$TMUX_PANE" -u @claude_message 2>/dev/null
     printf '\ePtmux;\e\e]7777;claude;off;%s\a\e\\' "$TMUX_PANE" > "$t"
     exit 0
   fi
   tmux set-option -p -t "$TMUX_PANE" @claude_status "$s" 2>/dev/null
   if [ -n "$m" ]; then
     b=$(printf %s "$m" | base64 | tr -d '\n')
     tmux set-option -p -t "$TMUX_PANE" @claude_message "$b" 2>/dev/null
     printf '\ePtmux;\e\e]7777;claude;%s;%s;%s\a\e\\' "$s" "$TMUX_PANE" "$b" > "$t"
   else
     printf '\ePtmux;\e\e]7777;claude;%s;%s\a\e\\' "$s" "$TMUX_PANE" > "$t"
   fi
   Then merge these into hooks in ~/.claude/settings.json (status token per event):
   - UserPromptSubmit += muxpad-status.sh running
   - SessionStart, PreToolUse += muxpad-status.sh auto (conditional — flips only needs_input/fresh start to running; keeps idle)
   - Stop += muxpad-status.sh idle (2nd arg = completion phrase, default "🎯 Done". Do NOT map SubagentStop — it would flip the session to idle mid-work)
   - Notification += muxpad-status.sh needs_input (matcher "permission_prompt" — permission requests only, so the idle reminder never overrides idle)
   - SessionEnd += muxpad-status.sh off
6. Test: run each script once with a test argument, and show me a summary of everything you changed.

3. Recommended ~/.tmux.conf

MuxPad works out of the box with your existing tmux config — it does not change your bindings or prefix. Two optional one-liners unlock extra features:

set -g mouse on              # two-finger scroll & pane selection (touch)
set -g set-clipboard on      # OSC 52 — let tmux copy to the iOS clipboard
set -g allow-passthrough all  # let servers push notifications to your device (OSC 9/777)
set -g history-limit 50000   # larger scrollback for ⌘F copy-mode search
set -g allow-rename off      # keep programs from renaming your windows (tabs)
set -g automatic-rename off

Reload the config without restarting tmux:

tmux source-file ~/.tmux.conf

Or, from inside a running tmux session, press your prefix (Ctrl-b by default), then type : and run source-file ~/.tmux.conf.

Using vim / nvim (optional)

If you live in vim/nvim in the terminal, these few lines noticeably improve the editing feel.

setw -g mode-keys vi                              # vi keys in copy-mode
set -sg escape-time 10                            # kill Esc delay (snappier mode switching)
set -ga terminal-overrides ",xterm-256color:Tc"  # truecolor (24-bit)
set -g focus-events on                            # nvim autoread & gitsigns

4. Server-pushed notifications (optional)

With set -g allow-passthrough all, any script on the server can send a titled notification to your device using an OSC 777 notify sequence wrapped in tmux passthrough. This is ideal for signalling that a long build or a remote AI run has finished — for example from a Claude Code notification hook:

printf '\ePtmux;\e\e]777;notify;Build finished;All tests passed\a\e\\' \
  > "$(tmux display -p '#{pane_tty}')"

Use all rather than on: on only passes sequences from panes that are currently visible, and notifications matter precisely when you are looking somewhere else.

Claude Code hook setup

To get a notification whenever Claude Code needs your attention, save this script on the server (e.g. ~/.claude/hooks/muxpad-notify.sh, chmod +x):

#!/bin/sh
# Sends a titled notification to MuxPad through tmux (OSC 777).
[ -n "$TMUX_PANE" ] || exit 0
title="${1:-Claude Code}"; body="${2:-Needs your attention}"
printf '\ePtmux;\e\e]777;notify;%s;%s\a\e\\' "$title" "$body" \
  > "$(tmux display -p -t "$TMUX_PANE" '#{pane_tty}')"

Then register it in ~/.claude/settings.json:

{
  "hooks": {
    "Notification": [{ "hooks": [{ "type": "command",
      "command": "~/.claude/hooks/muxpad-notify.sh 'Claude Code' 'Waiting for your input'" }] }],
    "Stop": [{ "hooks": [{ "type": "command",
      "command": "~/.claude/hooks/muxpad-notify.sh 'Claude Code' 'Task finished'" }] }]
  }
}

MuxPad also raises a notification on the terminal bell, and can detect when output goes idle to tell you a long-running job has likely finished — no server setup required for those two.

Claude status badge (optional)

MuxPad can show Claude Code's status as a sparkle badge on each Mux row in the sidebar — working (blue, pulsing), waiting for input (orange), or idle (gray). If you run Claude in several panes within one Mux, it shows the status of the pane with keyboard focus. With no hook installed or Claude not running, no badge appears (your sidebar stays exactly as it is). Alongside the badge, a one-line status message appears under the host — the prompt you typed while working, what Claude is asking for while waiting, and the completion phrase when idle.

Two ways to install. ① The easiest is to paste the setup prompt in section 2-b above (or "Copy setup prompt" on the app's first screen) — it installs and registers this status hook too. ② To do it by hand, follow the steps below.

Save a status hook script, separate from the notification one (e.g. ~/.claude/hooks/muxpad-status.sh, chmod +x). It raises no notification — it only records status on a dedicated sequence (OSC 7777) and a tmux pane option.

#!/bin/sh
# Emits Claude status + message to MuxPad (dedicated OSC 7777 + tmux pane option — not a notification).
# Message: running=prompt prefix, needs_input=what Claude asks, idle=completion phrase ($2, default "🎯 Done"). Requires python3.
[ -n "$TMUX_PANE" ] || exit 0
s="${1:-running}"; m=""
if [ "$s" = auto ]; then
  cur=$(tmux show-options -p -t "$TMUX_PANE" -v @claude_status 2>/dev/null)
  case "$cur" in running|idle) exit 0 ;; esac
  s=running
fi
j(){ python3 -c 'import sys,json;print(" ".join(str(json.load(sys.stdin).get(sys.argv[1],"") or "").split())[:120])' "$1" 2>/dev/null; }
case "$s" in
  running) m=$(j prompt) ;;
  needs_input)
    m=$(j message)
    case "$m" in *"waiting for your input"*) exit 0 ;; esac
    ;;
  idle) m="${2:-🎯 Done}" ;;
esac
t=$(tmux display -p -t "$TMUX_PANE" '#{pane_tty}')
if [ "$s" = off ]; then
  tmux set-option -p -t "$TMUX_PANE" -u @claude_status 2>/dev/null
  tmux set-option -p -t "$TMUX_PANE" -u @claude_message 2>/dev/null
  printf '\ePtmux;\e\e]7777;claude;off;%s\a\e\\' "$TMUX_PANE" > "$t"
  exit 0
fi
tmux set-option -p -t "$TMUX_PANE" @claude_status "$s" 2>/dev/null
if [ -n "$m" ]; then
  b=$(printf %s "$m" | base64 | tr -d '\n')
  tmux set-option -p -t "$TMUX_PANE" @claude_message "$b" 2>/dev/null
  printf '\ePtmux;\e\e]7777;claude;%s;%s;%s\a\e\\' "$s" "$TMUX_PANE" "$b" > "$t"
else
  printf '\ePtmux;\e\e]7777;claude;%s;%s\a\e\\' "$s" "$TMUX_PANE" > "$t"
fi

Then register it in ~/.claude/settings.json, with a status token per event:

{
  "hooks": {
    "SessionStart":     [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/muxpad-status.sh auto" }] }],
    "UserPromptSubmit": [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/muxpad-status.sh running" }] }],
    "PreToolUse":       [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/muxpad-status.sh auto" }] }],
    "Stop":             [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/muxpad-status.sh idle" }] }],
    "Notification":     [{ "matcher": "permission_prompt", "hooks": [{ "type": "command", "command": "~/.claude/hooks/muxpad-status.sh needs_input" }] }],
    "SessionEnd":       [{ "hooks": [{ "type": "command", "command": "~/.claude/hooks/muxpad-status.sh off" }] }]
  }
}

OSC updates the badge instantly, and the @claude_status pane option restores it within a few seconds after reconnecting or relaunching the app. It works with polling alone (about 5s) even when allow-passthrough is off.

5. Connect from MuxPad

MuxPad models a connection as three linked pieces — a Host, a User, and a Mux:

  1. Add a Host — the endpoint (address and port).
  2. Add a User — reusable credentials (a username with a password or key). One User can be shared across many Hosts and Muxes.
  3. Create a Mux — pick a host and a user, then give it a name. The Mux name is the tmux session name.
  4. Tap the Mux. MuxPad SSHes in and starts or attaches the tmux session automatically.

On connect, MuxPad auto-detects tmux. If it is not installed, the connection is blocked and setup guidance is shown. Because the Mux name maps to the tmux session name, tapping a Mux whose name matches an existing server-side session reattaches to it — that is how a session survives quitting the app, and how you continue an iPad session on iPhone.

6. Troubleshooting