macOS · zsh · tmux

Your Claude sessions, reachable from anywhere.

You start three Claude Code sessions in iTerm, get deep into all of them, and then realise none are reachable from anywhere but that one window. claude2tmux moves them into tmux — detach, close the laptop, pick them back up over SSH.

claude2tmux --list
$ claude2tmux

  iTerm2 window 1, 5 tab(s) -> tmux session 'claude': (1 other iTerm2 window(s) not touched)

  MOVE   tab 1    ttys000  pid 39902  -> claude:D4G4
           ~/GitHub/D4G4  People don't realise how crappy their sound is…
  STAGE  tab 2    ttys001  pid 36339  -> claude:dg
           ~  this session
  --     tab 3    ttys014  zsh    no Claude session
  MOVE   tab 4    ttys003  pid 30259  -> claude:First-Ink
           ~/GitHub/D4G4/First-Ink  lets make a new build
  --     tab 5    ttys015  tmux   running tmux — its sessions are already in tmux

  MOVE  (2) SIGTERMed, then relaunched as claude --resume <id>.
        The iTerm2 tab stays open — only the Claude process is stopped.
  STAGE (1) window built, command typed but not entered — you quit the
        original and press Enter. Never killed for you.

  Listing only. Run --go to perform it.

The catch, up front

It does not move the running process. A live process's controlling terminal cannot be reassigned on macOS — the Linux tool for that (reptyr) works by ptrace-ing the target and swapping its TTY, and there is no working macOS equivalent.

So claude2tmux migrates the session, not the process. Claude Code appends every message to ~/.claude/projects/<slug>/<id>.jsonl as it happens, so killing a session and running claude --resume <id> inside tmux restores the full conversation, working directory, and history.

What survives: everything already written to the transcript.
What is lost: a tool call that was in flight at the moment of the kill.

If that trade isn't acceptable for a given session, --no-kill builds its tmux window and stages the resume command without touching the original — you quit it yourself once it's idle.

Install

Homebrew

$ brew install daksh-gargas/tap/claude2tmux

The full user/tap/formula name is deliberate. Since Homebrew 6.0.0 a formula from a non-official tap has to be trusted before Homebrew will load it — a formula is executable Ruby, not metadata — and naming it in full is that grant, for this one formula. The two-step brew tap + brew install claude2tmux form stops with a brew trust error instead.

Direct from GitHub

One file, no dependencies beyond tmux and python3.

$ curl -fsSL https://raw.githubusercontent.com/daksh-gargas/claude2tmux/main/claude2tmux \ -o /usr/local/bin/claude2tmux && chmod +x /usr/local/bin/claude2tmux

Or grab the latest release tarball and install -m 755 claude2tmux /usr/local/bin/.

Optional: a c2t shortcut

$ claude2tmux --install-c2t && exec zsh

Writes a marked block to ~/.zshrc, so re-running updates it in place instead of appending a second copy. It backs the file up first, and refuses to install if you already define c2t elsewhere. --uninstall-c2t restores the file byte-for-byte.

Use

$ claude2tmux # list what would move — the default, changes nothing $ claude2tmux --go # do it

The listing states a disposition per session, so what actually moves is never something you have to infer.

DispositionMeaning
MOVE SIGTERMed, then relaunched as claude --resume <id> in a tmux window at the same cwd.
STAGE Window built, resume command typed but not entered. Never killed for you.
-- A tab with no Claude session in it — a shell, tmux, an editor. Listed so you can see it was checked.
SKIP A session that exists but is not safely movable. Every reason is named.

It walks the tabs of the iTerm2 window you ran it from, so two whole classes of mistake are impossible by construction rather than by filtering. A Claude desktop app or VS Code session runs on a pty that is not an iTerm2 tab, so it can never be picked up. And a tab running tmux holds its Claude sessions on pane ttys rather than the tab's own tty, so sessions already in tmux are never touched.

The iTerm2 tab is left open — only the Claude process inside it is stopped, so the tab drops back to its shell prompt with its scrollback intact.

The session you run the command from is always STAGE, never MOVE — killing it would abort the migration mid-run. Quit that tab yourself, switch to its window, press Enter.

Flags

-l, --list list every session and its disposition; changes nothing --plain with --list: tab-separated for scripting --go perform the migration --no-kill with --go: build the windows, leave the originals running --system ignore iTerm2; scan every CLI session on the machine -s, --session N target tmux session name (default: claude) --install-c2t add a `c2t` shell function to ~/.zshrc --uninstall-c2t remove it again -h, --help usage

Bare claude2tmux is identical to --list, and --list overrides --go — asking to see the plan never executes it. An unrecognised flag exits 2 rather than being ignored, since a typo next to --go would otherwise be a bad surprise.

How a session is detected

Every hop is a fact rather than a guess: tab → tty → the claude on that tty → ~/.claude/sessions/<pid>.json → id. iTerm2 is asked for its tabs and hands back each tab's tty; the session's own id is read from the registry record it writes for itself, never inferred from the working directory — one directory can hold several sessions at once. The full write-up, listings included, is in You can't move a terminal, so move the session.