https://github.com/binaryphile/tmux-claude
A TPM-installable tmux plugin: session manager + coordinator for multiple Claude Code panes (single host, multi-tmux-session, optional cross-host via era inbox streams)
https://github.com/binaryphile/tmux-claude
Last synced: 14 days ago
JSON representation
A TPM-installable tmux plugin: session manager + coordinator for multiple Claude Code panes (single host, multi-tmux-session, optional cross-host via era inbox streams)
- Host: GitHub
- URL: https://github.com/binaryphile/tmux-claude
- Owner: binaryphile
- License: mit
- Created: 2026-06-02T03:33:03.000Z (20 days ago)
- Default Branch: main
- Last Pushed: 2026-06-02T04:50:32.000Z (20 days ago)
- Last Synced: 2026-06-02T06:23:09.288Z (20 days ago)
- Language: Shell
- Size: 42 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# tmux-claude
A tmux plugin that turns tmux into a session manager and coordinator for
multiple Claude Code panes.
## Why
tmux is great at managing panes. Claude Code is great at being in a pane.
But the seams aren't quite right:
- A claude pane crashes, you reboot, you restart tmux — your sessions are
gone. tmux-resurrect can bring back the *pane* but not the *Claude
session attached to it*.
- You have 6 claude panes running across 3 projects. Which one is waiting
for you? Which is mid-flow? You alt-tab through them all to find out.
- You want claude-A in pane 0 to send a status ping to claude-B in pane
3, both working on the same epic. There's no in-tmux primitive for
that.
tmux-claude is the in-tmux glue for these jobs. It does:
- **Session restore with the right `--resume `** for each pane,
derived from `~/.claude/projects//.jsonl`.
- **Status indicators** — pane border / status-bar color reflects whether
Claude is idle, working, or waiting for operator input.
- **Labeled spawn** — `tmux-claude spawn [label]` creates a new
claude pane in a project with an optional role label (auto-generated A..Z
if omitted; collision-rejected against currently-active labels).
- **Cross-pane messaging** (when `era`/`evtctl` are installed) — one
claude can send another a message via the inbox-stream pattern.
## Status
**UC1 (View Pane Topology) + UC3 (Spawn Labeled Pane) + UC4 (Identify
Waiting Pane) + UC5 (Switch Focus to Next Waiting Pane) shipped.** See
`docs/use-cases.md` for behavioral contracts and `docs/design.md` for
mechanisms. Remaining UCs (restore, messaging, snapshot, label rename/
remove, ambient state indicators) are queued; see `CLAUDE.md` for scope
tiers.
## Usage
```bash
# from anywhere inside a tmux session with tmux-claude on PATH:
tmux-claude topology # render the topology
tmux-claude next-waiting # jump focus to the next pane in `waiting` state
tmux-claude spawn [label] # create a new claude pane in , with optional label
```
Topology output (state and AGE driven by UC4's projection over Claude Code hook events):
```
PANE PROJECT SESSION LABEL STATE AGE
%3 sofdevsim-2026 04828084-ca16-4400-af2d-86392a1efeab — working 12s
%4 tmux-claude b9c81921-26ac-448b-8be3-2204cb4d15bc — waiting 3m
%5 era 1beae198-5024-47bb-9065-c4aa5c6c7898 — n/a
```
State vocabulary: `working` (claude mid-turn), `waiting` (turn ended /
permission needed → operator action expected), `` (no recent
state event observed for this session_id, or state knowledge is stale).
`—` (em-dash) in the LABEL column means no label is bound to that
session_id; running `tmux-claude spawn [label]` is the way
to bind one.
### Spawning labeled panes (UC3)
```bash
tmux-claude spawn ~/projects/era era-A # explicit label
tmux-claude spawn ~/projects/jeeves # auto-label (first unused letter A..Z)
```
The spawn flow: validate the project directory, check `claude` is on
PATH, allocate the label (collision-rejected; auto-generated if not
supplied), `tmux new-window` in the project, poll for the new claude
session's `.jsonl` (up to `TMUX_CLAUDE_SPAWN_TIMEOUT`), publish a
`label-assigned` event that UC1's topology view consumes. Any
post-spawn failure (timeout, ambiguous attribution under concurrent
.jsonl creation, era publish error) kills the newly-spawned window so
no partially-labeled pane is left behind (UC3 Minimum Guarantee).
### Configuration knobs
| Env var | Default | Meaning |
|---|---|---|
| `TMUX_CLAUDE_STALE_SECONDS` | `3600` | After this many seconds without a state-changing hook event, the state collapses to ``. Set to `0` to disable (states persist indefinitely). |
| `TMUX_CLAUDE_SPAWN_TIMEOUT` | `10` | Seconds the spawn command polls for the new claude session's `.jsonl` before timing out. Non-integer or negative treated as default; `0` disables (immediate timeout — useful for debugging). |
## Keybinding
When loaded via TPM (or `tmux run-shell ~/path/to/tmux-claude.tmux`), the
plugin binds:
| Key | Action |
|---|---|
| `prefix + ?` | Show topology in a `display-popup` (tmux ≥ 3.0; falls back to `display-message` on older tmux) |
| `prefix + N` | Jump focus to the next claude pane in `waiting` state. Round-robins through waiting panes in numeric pane-id order (`%2` before `%10`), starting after the currently-focused pane with wrap-around. `display-message` reports edge cases (no waiting panes; only the current pane is waiting; target pane vanished). |
## Install
Via [TPM](https://github.com/tmux-plugins/tpm):
```tmux
set -g @plugin 'binaryphile/tmux-claude'
```
Then `prefix + I` to install. TPM clones the plugin into
`~/.tmux/plugins/tmux-claude/` and runs `tmux-claude.tmux` via
`tmux run-shell`.
Manual install:
```bash
git clone https://github.com/binaryphile/tmux-claude ~/.tmux/plugins/tmux-claude
echo 'run-shell ~/.tmux/plugins/tmux-claude/tmux-claude.tmux' >> ~/.tmux.conf
tmux source-file ~/.tmux.conf
```
(The `.tmux` file is a bash script — TPM and tmux's `run-shell` execute
it; `source-file` would attempt to parse it as a tmux config and fail.)
## License
TBD.