dorkhub

tmux-claude-session-manager

Run many Claude Code sessions across your projects, each in its own tmux session — then list them, see which are done v…

craftzdog
Shell33344 forksMITupdated 5 days ago
visit the demogit clone https://github.com/craftzdog/tmux-claude-session-manager.gitcraftzdog/tmux-claude-session-manager

tmux-claude-session-manager

screenshot

Run many Claude Code sessions across your projects, each in its own tmux session — then list them, see which are done vs. still working, and jump to one from a single popup.

If you launch Claude per-directory (one nested session per project), you quickly end up with a dozen of them and no way to tell which are finished without opening each one. This plugin gives you:

  • 🔢 A central picker (prefix + u) listing every running Claude agent — several in one project, and any running loose in an ordinary pane.
  • 🟢 Live status per agent — working / waiting / idle — read straight from claude agents --json, so you instantly see which need you. No setup.
  • 👁️ A live preview of each agent's screen right in the picker.
  • 🎯 Smart jump — selecting an agent switches your client to the window it was launched from, then resumes it in a popup over it.
  • 🚀 A launcher (prefix + y) that opens/attaches a Claude session for the current directory.
  • Quick kill (ctrl-x) of a finished agent from the picker.

Status needs no configuration. Claude Code publishes each agent's own state and the picker reads it — there are no hooks to install.

Prerequisites

  • tmux ≥ 3.2 (for display-popup)
  • fzf — the picker UI
  • jq — parses claude agents --json
  • Claude Code ≥ 2.1.139 — for the claude agents command (claude --version to check)
  • bash; macOS or Linux

Install (tpm)

Add to ~/.tmux.conf (or ~/.config/tmux/tmux.conf):

set -g @plugin 'craftzdog/tmux-claude-session-manager'

Then hit prefix + I to install.

Keybinding note: by default the plugin binds prefix + y (launch) and prefix + u (list). If your config binds those elsewhere, either change the options below, or make sure the plugin loads after your own bindings (put run '~/.tmux/plugins/tpm/tpm' after them) so the one you want wins.

Manual install

git clone https://github.com/craftzdog/tmux-claude-session-manager ~/clone/path

Add to ~/.tmux.conf, then reload (prefix + r or tmux source ~/.tmux.conf):

run-shell ~/clone/path/claude_session_manager.tmux

Usage

Key Action
prefix + y Launch (or re-attach to) a Claude session for the current directory, in a popup
prefix + u Open the agent picker

Inside the picker:

Key Action
enter Jump to the agent (see How it works)
ctrl-x Kill the highlighted agent
/ , type to filter fzf navigation

Agents needing your attention (waiting, idle) sort to the top.

Every running Claude gets its own row — the picker identifies each by its process, not by its tmux session. So several agents in one project all show up separately, as does a Claude you started by hand in an ordinary pane.

Options

Set any of these before the plugin loads (defaults shown):

set -g @claude_launch_key     'y'        # prefix key: launch/open for current dir
set -g @claude_list_key       'u'        # prefix key: open the picker
set -g @claude_command        'claude'   # command run in new sessions
set -g @claude_args           ''         # extra args appended to the command
set -g @claude_session_prefix 'claude-'  # tmux session name prefix
set -g @claude_popup_width     '90%'     # popup width
set -g @claude_popup_height    '90%'     # popup height
set -g @claude_fzf_options    ''         # extra options passed to the fzf picker

For example, to skip permission prompts in launched sessions:

set -g @claude_args '--dangerously-skip-permissions'

Customizing the fzf picker

@claude_fzf_options is passed straight to fzf, so you can add your own bindings.

Here is a vim keybinding example:

set -g @claude_fzf_options "\
  --prompt 'nav> ' \
  --bind 'j:down' \
  --bind 'k:up' \
  --bind 'q:abort' \
  --bind 'x:execute-silent(kill {3})+reload(sleep 0.3; \$CLAUDE_PICKER --list)' \
  --bind 'i:unbind(j,k,q,i,a,x)+change-prompt(filter> )' \
  --bind 'a:unbind(j,k,q,i,a,x)+change-prompt(filter> )' \
  --bind 'esc:rebind(j,k,q,i,a,x)+change-prompt(nav> )'"

The picker opens in nav mode:

Key Action
j / k move down / up
i / a switch to filter mode — type to fuzzy-match
x kill the highlighted agent (like the built-in ctrl-x)
q close the picker
enter jump to the agent (both modes)
esc filter mode → back to nav

Only the bound keys are special in nav mode; any other key still filters as you type. x reloads the list through $CLAUDE_PICKER, a path the picker exports for exactly this — write it as \$CLAUDE_PICKER inside the double-quoted value above so tmux stores a literal $ (in a single-quoted value, use a bare $CLAUDE_PICKER).

How it works

  • The launcher creates a detached claude-<hash-of-dir> tmux session running claude, records the window it came from in @claude_origin, and attaches to it in a popup.
  • claude agents --json is the source of truth for what is running and how it is doing. Each Claude session self-reports its state (busy / waiting / idle) to a supervisor daemon, which that command publishes. Nothing here scans processes for a claude command name — on macOS a pane reports its parent shell, never the claude child running inside it.
  • agents.sh pairs each running Claude with the tmux pane it occupies by joining pidtty → pane. That join is why identity is the Claude process rather than the tmux session, and therefore why several agents in one project each get their own row. It costs three subprocesses per render, whatever the number of sessions or panes.
  • The age column is the mtime of the agent's transcript — its last sign of life. claude agents --json reports only startedAt, never a last-activity time. A brand-new agent that has yet to take a turn shows -.
  • The picker renders those rows with a live capture-pane preview. On enter a dedicated agent (in a claude-* session) resumes in the popup over the window it was launched from, while a loose one (any other pane) is focused in place. ctrl-x kills the Claude process itself: a dedicated session dies with its last window, and a loose pane keeps the shell that hosted it.
  • Pressing prefix + u from inside a session popup detaches that popup first (closing it), then reopens the picker full-size on the outer host client — so you never end up with a cramped popup-in-popup.

License

MIT © Takuya Matsuyama

more like this

agentlytics

Comprehensive analytics dashboard for AI coding agents — Cursor, Windsurf, Claude Code, VS Code Copilot, Zed, Antigravi…

JavaScript555
@f

veille-techno

Skill Claude Code de veille tech francophone. Agrège les flux RSS (Journal du Hacker, Human Coders News…) et produit un…

Python51

xylocopa

A to-do list that runs your Claude Code agents — capture anywhere, dispatch in parallel, review from your phone.

Python51

search

search projects, people, and tags