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 tmux | Fix |
|---|---|
| No desktop notifications | allow-passthrough |
| Shift+Enter submits instead of adding a newline | extended-keys, or use Ctrl+J |
| Ctrl+B doesn’t background a command | Press it twice |
| Mouse wheel does nothing | set -g mouse on |
| tmux copy mode can’t find the conversation | Ctrl+O, then [ |
| Colors look flat | Turn on truecolor |
| Can’t tell which window needs you | Bell 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:
ononly works for visible panes. A Claude session in a window you aren’t looking at can’t pass anything through. That is exactly the session you want to hear from. On tmux 3.4 or later, useset -g allow-passthrough allso hidden panes can reach the terminal too. The trade-off: any program in any pane can now write those sequences to your terminal.- Version.
allow-passthrougharrived in tmux 3.3 and theallvalue in 3.4. Check yours withtmux -V. - A detached session has no terminal. If nobody is attached, nothing reaches you. Hooks that call
osascriptornotify-senddon’t depend on the terminal, so they still work. The setup is in How to get Claude Code notifications.
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:
- Ctrl+B moves a running Bash command or agent to the background in Claude Code. It’s also tmux’s default prefix, so press it twice: tmux sends the second one through.
- Esc interrupts Claude. Before tmux 3.5 the default
escape-timewas 500 ms, which makes Esc feel laggy and can turn Esc followed by a key into an Alt shortcut. tmux 3.5 lowered the default to 10 ms; on older versions setset -s escape-time 10. /terminal-setupwrites keybindings into your terminal’s own config. Run it in a terminal tab outside tmux, or it can’t find the right file.- Option shortcuts on macOS, such as Option+P to switch models, need your terminal to send Option as Meta (“Esc+” in iTerm2). tmux passes them on once the outer terminal sends them.
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.
- Fullscreen rendering draws on the terminal’s alternate screen, like
vim. It flickers less in tmux, but the conversation isn’t in tmux’s history. Scroll with the mouse wheel or PgUp/PgDn. For the wheel to reach Claude Code, tmux needsset -g mouse on; without it the wheel goes to tmux and Claude Code shows a one-time hint about it. - The classic renderer writes the conversation into normal scrollback, so tmux copy mode (prefix then
[) and search work as usual. Switch with/tui default, or start a single session withCLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1 claude. tmux keeps 2,000 lines per pane by default, which a long session fills quickly, so raisehistory-limit.
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:
- Name windows after the directory automatically.
set -g automatic-rename-format '#{b:pane_current_path}'uses the folder name for every window you don’t name yourself. - Show what Claude is working on. Claude Code sets the terminal title from the conversation. In tmux that becomes the pane title,
#{pane_title}or#T, which you can put instatus-rightor a pane border. - Panes instead of windows.
tmux split-window -h -c ~/code/webputs two sessions side by side. It’s fine for two; past three the input boxes get too narrow to read. - Worktrees.
claude --worktree feature-x --tmuxcreates a git worktree and opens Claude in a new tmux session for it. It uses iTerm2’s native panes when it can; add--tmux=classicfor plain tmux.
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:
- Notifications follow the client. While you’re attached over SSH, passthrough notifications reach the terminal on your laptop. While detached, none arrive, so a hook that pings your phone is the only way to hear from the server.
- A stalled connection can freeze Claude. If the SSH link stalls or an iTerm2
tmux -CCpane is paused, Claude Code can block on writing to the terminal. Starting it withCLAUDE_CODE_NONBLOCKING_STDOUT=1(Claude Code v2.1.261 or later, macOS, Linux and WSL) stops that. - Clickable links can lose their link over SSH and tmux. Claude Code can’t always detect hyperlink support there;
FORCE_HYPERLINK=1 claudeoverrides the check. - Windows: tmux doesn’t run natively on Windows. Run it and Claude Code inside WSL.
9. Common problems
- Config changes do nothing. Editing
~/.tmux.confdoesn’t reload it. Runtmux source-file ~/.tmux.conf, and forfocus-eventsdetach and attach again. - Notifications only arrive for the window you’re looking at. That is
allow-passthrough onworking as designed. Useallon tmux 3.4 or later. - Shift+Enter still submits. Check that your terminal’s
TERMoutside tmux matches theterminal-featurespattern, then restart Claude Code. Ctrl+J always works. - The hook never flags anything. Run
echo '{}' | ~/.claude/tmux-flag.sh oninside a tmux pane. If that works, check/hooks. If the status line doesn’t change, thewindow-status-formatlines didn’t load. - The window name keeps changing. Automatic renaming is on for windows you didn’t name. Name them with
-norrename-window, or setautomatic-rename-format. - The status line in Claude Code is cut off. Narrow panes truncate it. Zoom the pane with prefix then
z, or shorten your Claude Code status line.
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:
- Each Claude Code, Codex, OpenCode and omp session gets a pet that walks along the bottom of every page, with a capsule saying Thinking, Your Turn, Allow Bash? or Done. The toolbar badge counts sessions waiting on you.
- When a session asks for permission, click its pet and answer Allow, Always or Deny without switching to tmux. The prompt in the tmux window keeps working too.
- Read a session’s latest answer and send its next message from the tab you’re on.
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.