macOS · zsh · tmux
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 Sessions that will move into tmux session 'claude': MOVE ttys000 pid 39902 -> claude:D4G4 ~/GitHub/D4G4 People don't realise how crappy their sound is… STAGE ttys001 pid 36339 -> claude:dg ~ give me a flag which lists the sessions that will be moved MOVE ttys002 pid 30842 -> claude:Wildr ~/GitHub/Wildr Theme set to auto MOVE ttys003 pid 30259 -> claude:First-Ink ~/GitHub/D4G4/First-Ink lets make a new build MOVE ttys005 pid 57289 -> claude:First-Ink-2 ~/GitHub/D4G4/First-Ink commit your work to main MOVE (4) SIGTERMed, then relaunched as claude --resume <id>. 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.
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.
One file, no dependencies beyond tmux and python3.
Or grab the
latest release tarball
and install -m 755 claude2tmux /usr/local/bin/.
c2t shortcut
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.
The listing states a disposition per session, so what actually moves is never something you have to infer.
| Disposition | Meaning |
|---|---|
| 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. |
| SKIP | Already inside a tmux pane, or has no controlling terminal. Counted, not listed — there is nothing to do. |
A session with no controlling TTY is not a terminal session at all — the
VS Code or JetBrains extension host, a claude -p run, a hook.
There is nothing to migrate, and SIGTERMing one would kill a session you
cannot see in any tab, so they are excluded before anything else happens.
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.
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.
A candidate has to survive four filters. Three are structural facts; the fourth is a heuristic, and it's worth knowing which is which.
argv[0] basename is exactly claude/tmp/claude-*-cwd redirects —
and grepping would sweep all of them in. This uses shell parameter
expansion rather than basename, because login shells are
named -zsh and basename parses that as a flag.
claude ancestorclaude ancestor of
$$.
tmux list-panes -a -F '#{pane_tty}' directly.
Without it, sessions already safely in tmux get killed and rebuilt for no
reason.
lsof, since macOS has no /proc. Session
ID from --resume <uuid> in argv when present —
definitive, because resuming appends to the same transcript rather than
forking a new one.
Filter 4's fallback is the soft spot. For a session started without
--resume, the ID is recovered by matching the recorded
cwd across transcripts and taking the most recently written.
If you ran two sessions in one directory and killed one, the survivor could
in principle be handed the dead one's ID. In practice the live session is
always the most recently written — but it is inference, not a fact read off
the process. --list shows you the resolved ID and the last
user message of every session, so you can check before committing.