Using Claude Code in tmux

Updated 24 October 2026

tmux is the easy way to keep several Claude Code sessions alive, one per project, and come back to them from another terminal or over SSH. Out of the box, though, tmux eats Claude Code’s notifications, turns Shift+Enter into Enter, clamps colors to 256 and hides which window is waiting for you. A few lines of config fix most of it. This guide covers each one, then a hook that flags the tmux window when Claude needs input.

Quick fixes

Problem in tmuxFix
No desktop notificationsallow-passthrough
Shift+Enter submits instead of adding a newlineextended-keys, or use Ctrl+J
Ctrl+B doesn’t background a commandPress it twice
Mouse wheel does nothingset -g mouse on
tmux copy mode can’t find the conversationCtrl+O, then [
Colors look flatTurn on truecolor
Can’t tell which window needs youBell flags and a hook

If you just want the config, this is everything from the sections below in one block:

~/.tmux.conf# Claude Code
set -g allow-passthrough on        # "all" on tmux 3.4+ for hidden windows
set -g focus-events on
set -s extended-keys on
set -as terminal-features 'xterm*:extkeys'
set -g mouse on
set -s escape-time 10
set -g history-limit 50000
set -ga terminal-overrides ',*:Tc'

Apply it to the running server with tmux source-file ~/.tmux.conf. tmux only picks up focus-events after you detach and attach again, so do that too, then restart Claude Code.

1. Notifications: allow passthrough

Claude Code’s built-in desktop notifications (Ghostty, Kitty and iTerm2) and its progress bar are escape sequences written to the terminal. tmux sits in between and drops them unless you let them through:

~/.tmux.confset -g allow-passthrough on
set -g focus-events on

Three details catch people out:

Over SSH, the notification travels back through SSH to the terminal on your own machine. Ghostty and Kitty show it without extra setup; iTerm2 needs Notification Center Alerts turned on under Settings → Profiles → Terminal.

2. Shift+Enter and other keys

Inside tmux, Shift+Enter sends a plain Enter and submits your prompt, even when your terminal supports it outside tmux. These two lines let tmux tell them apart:

~/.tmux.confset -s extended-keys on
set -as terminal-features 'xterm*:extkeys'

The xterm* pattern matches the TERM of your outer terminal. If your terminal sets something else, such as xterm-ghostty (which xterm* already covers) or alacritty, add a line for that name.

Two newline keys work everywhere with no setup: Ctrl+J, or \ then Enter. Use them if you’d rather not touch tmux at all.

Other keys that behave differently inside tmux:

3. Scrollback, mouse and copying

How scrolling works depends on which renderer Claude Code uses. Run /tui with no argument to see which one you’re on.

To search a fullscreen session with tmux, press Ctrl+O for transcript mode, then [. Claude Code writes the whole conversation into the terminal’s scrollback with tool output expanded, and tmux copy mode can see it until you leave transcript mode.

In fullscreen, Claude Code captures the mouse. When you drag to select, it copies the text to your system clipboard and to the tmux paste buffer, so prefix then ] pastes it. If you want tmux’s own selection back, start Claude Code with CLAUDE_CODE_DISABLE_MOUSE=1; you keep keyboard scrolling but lose clicks and wheel scrolling inside Claude Code.

iTerm2 users: fullscreen rendering doesn’t work in iTerm2’s tmux integration (tmux -CC): the wheel does nothing and double-click can corrupt the screen. Claude Code starts in the classic renderer there by default. Plain tmux inside iTerm2 is fine.

4. Colors and flicker

When $TMUX is set, Claude Code limits itself to 256 colors, because tmux doesn’t pass 24-bit color through unless told to. If your terminal supports truecolor, tell tmux, then tell Claude Code:

~/.tmux.confset -ga terminal-overrides ',*:Tc'
~/.zshrc or ~/.bashrcexport CLAUDE_CODE_TMUX_TRUECOLOR=1

Any non-empty value turns it on, including 0 and false. To go back to 256 colors, unset the variable.

If the screen flickers while Claude streams output, the cause is usually tmux, not Claude Code: tmux through the 3.6 series doesn’t support synchronized output, which terminals use to draw a frame in one go. tmux 3.7 added it. Until you can upgrade, fullscreen rendering (/tui fullscreen) redraws far less and helps the most.

5. One session per window, named after the project

The layout that stays readable is one tmux window per project, each running its own Claude Code. Give the window a name when you create it: tmux then turns off automatic renaming for that window, so it keeps the project name instead of the name of whatever process is running. This script starts a session called agents with three projects, or attaches if it already exists:

~/bin/agents#!/bin/bash
session=agents
projects=(api web docs)

if ! tmux has-session -t "$session" 2>/dev/null; then
  tmux new-session -d -s "$session" -n "${projects[0]}" -c ~/code/"${projects[0]}"
  for p in "${projects[@]:1}"; do
    tmux new-window -t "$session" -n "$p" -c ~/code/"$p"
  done
  for p in "${projects[@]}"; do
    tmux send-keys -t "$session:$p" "claude -n $p" Enter
  done
fi

tmux attach -t "$session"

claude -n sets the Claude Code session’s display name, so you can find it again later with claude --resume api. Starting Claude with send-keys rather than as the window’s command means the window drops back to a shell when Claude exits, instead of closing.

Other ways to lay it out:

6. See which window needs you

tmux already marks windows that rang the bell. monitor-bell is on by default: when a program in a background window rings the bell, tmux adds ! to that window in the status line and shows its name in reverse colors. Prefix then M-n (Alt+N) jumps to the next window with an alert.

All you need is for Claude Code to ring the bell. The simplest way is its built-in bell channel:

~/.claude/settings.json{
  "preferredNotifChannel": "terminal_bell"
}

This follows the same timing as Claude Code’s notifications: a permission prompt rings after about six seconds without typing, and “waiting for you” about 60 seconds after Claude finishes. It also replaces the desktop notification in Ghostty, Kitty and iTerm2. For an immediate flag, use the hook in the next section.

monitor-activity is less useful. It flags a window whenever anything is printed in it, and Claude Code redraws its spinner the whole time it works, so the flag means “busy”, not “needs you”. Leave it off for Claude windows.

7. A hook that flags the window when Claude needs input

Claude Code hooks run with the same environment as Claude, so inside tmux they get $TMUX_PANE, the pane Claude is running in. This script sets a tmux variable on that window, shows a message in the status line and returns a bell for Claude Code to print, which sets the ! flag. Hooks have no terminal of their own, so the bell goes through Claude Code’s terminalSequence output field instead of being printed directly.

~/.claude/tmux-flag.sh#!/bin/bash
# Usage: tmux-flag.sh on | off
cat > /dev/null                 # Claude Code sends JSON on stdin; not needed here
[ -n "$TMUX_PANE" ] || exit 0   # not running inside tmux

if [ "$1" = on ]; then
  tmux set-option -w -t "$TMUX_PANE" @claude_waiting 1
  tmux display-message -d 4000 -t "$TMUX_PANE" "Claude needs you in #{window_name}"
  printf '{"terminalSequence":"\\u0007"}\n'
else
  tmux set-option -w -u -t "$TMUX_PANE" @claude_waiting
fi

Run chmod +x ~/.claude/tmux-flag.sh, then register it. PermissionRequest runs the moment Claude asks for permission, with no six-second wait. Stop runs when Claude finishes its turn. The PreToolUse entry covers multiple-choice questions, which don’t count as notifications. Sending a prompt or a tool finishing clears the flag:

~/.claude/settings.json{
  "hooks": {
    "PermissionRequest": [
      { "matcher": "", "hooks": [{ "type": "command", "command": "~/.claude/tmux-flag.sh on" }] }
    ],
    "PreToolUse": [
      { "matcher": "AskUserQuestion", "hooks": [{ "type": "command", "command": "~/.claude/tmux-flag.sh on" }] }
    ],
    "Stop": [
      { "hooks": [{ "type": "command", "command": "~/.claude/tmux-flag.sh on" }] }
    ],
    "UserPromptSubmit": [
      { "hooks": [{ "type": "command", "command": "~/.claude/tmux-flag.sh off" }] }
    ],
    "PostToolUse": [
      { "matcher": "", "hooks": [{ "type": "command", "command": "~/.claude/tmux-flag.sh off" }] }
    ]
  }
}

If you already have a "hooks" object, add these events inside it rather than a second one. Type /hooks in Claude Code to check they loaded. Then show the variable in the status line, keeping tmux’s default window format:

~/.tmux.confsetw -g window-status-format '#I:#W#{?window_flags,#{window_flags}, }#{?@claude_waiting,[needs you] ,}'
setw -g window-status-current-format '#I:#W#{?window_flags,#{window_flags}, }#{?@claude_waiting,[needs you] ,}'

Unlike the bell flag, which clears as soon as you look at the window, [needs you] stays until you answer. One limit: when Claude runs several tools at once, one finishing can clear the flag while another still waits for permission. The bell has already rung by then, so you still get the alert.

8. Detach and reattach over SSH

tmux keeps Claude Code running when you detach (prefix then d) or when the SSH connection drops. To get back to it, attach to the same session, or create it the first time, in one command:

Terminalssh -t devbox 'tmux new-session -A -s agents'

-A attaches if the session exists. -t gives SSH a terminal, which tmux needs. Things to know when you work this way:

9. Common problems

See every tmux window from your browser with Agent Pets

The tmux status bar only helps while you’re looking at the terminal. Agent Pets shows the same sessions in Chrome:

npx getagentpets init adds its hooks next to any you already have, including the tmux one above. Everything runs on your computer, with the helper on 127.0.0.1, so it’s for sessions on the same machine as your browser, not on a server you SSH into.

Get Agent Pets — was $25, now $6Get Agent Pets — $25

Questions

Why doesn’t Shift+Enter work in Claude Code inside tmux?

tmux sends Shift+Enter as a plain Enter unless extended keys are on. Add set -s extended-keys on and set -as terminal-features 'xterm*:extkeys' to ~/.tmux.conf and reload it. Ctrl+J inserts a newline without any setup.

How do I get Claude Code notifications inside tmux?

Add set -g allow-passthrough on to ~/.tmux.conf. With on, only the visible pane gets through; on tmux 3.4 or later, all lets sessions in hidden windows notify you too.

Does Claude Code keep running when I detach from tmux?

Yes. tmux keeps the session and every program in it running after you detach or your SSH connection drops. Run tmux new-session -A -s agents to attach again.

How do I see which tmux window Claude Code is waiting in?

Make Claude ring the bell, with preferredNotifChannel set to terminal_bell or a hook that returns a bell. tmux then marks that window with !, and prefix then Alt+N jumps to it.

Sources: Claude Code’s documentation on terminal configuration, fullscreen rendering, hooks, interactive mode and environment variables; claude --help (v2.1.287); the tmux manual, tmux CHANGES and option defaults.

More guides