One Mac mini, seven projects, one tmux session and about 80 lines of shell script. Survived a reboot, a kill -9 and a dead tmux server. Did not survive my attempt to automate the login.
Two weeks ago, seven Claude Code sessions were running on my Mac mini in seven Terminal tabs. Each one belonged to a project: Captain Drawdown, my Remy skill (my Google Docs skill for Claude), a home infrastructure project, a modelling project for enhanced weathering and some more. I talk to all of them from my phone through Remote Control in the Claude app.
It worked great, until the Mac restarted. Then all seven tabs were gone, and I had to sit down in front of the machine (or connect via screen sharing), open seven tabs, cd into seven folders and type the same command seven times.
I call the fix “Terminal Window Wrangling”: every agent gets a window in tmux, every window runs an endless loop, and macOS itself makes sure the windows exist. No framework, no orchestration layer, no Docker. Shell, tmux and one LaunchAgent.
I built this together with Claude Code itself, in one session, on the Mac mini it now manages. Here is what came out of it, what I tested and what didn’t work.
The goal: survive network loss, crashes and reboots without me
My requirements were short:
- Up to 12 sessions, one per project folder, each reachable via Remote Control.
- Survive network loss and a reboot without physical access.
- After a restart, continue the last conversation in that folder (
--continue). - Everything flat and readable. If it breaks at 2am, I want to understand it from my phone.
The machine already had the boring prerequisites: auto-login is on, FileVault is off (otherwise the Mac would wait for a password after a power outage), autorestart after power loss is on and sleep is off. If FileVault is on in your setup, this whole approach needs a different answer for the reboot case.
Three layers, each one restarting the layer below
The design has exactly three moving parts:
- A loop per agent (
run-agent.sh): startsclaude, waits until it exits, waits 30 seconds, starts it again. Forever. - A tmux session called
agentswith one window per agent. tmux keeps the interactive terminal alive without anybody being logged in at a Terminal window. - A LaunchAgent that runs
start-agents.shat login and then every 5 minutes. The script is idempotent: it creates the tmux session and any missing windows, and does nothing if everything is there.
The list of agents is a text file with one line per agent:
# name|pathRemy|/Users/numberone/DirksGDocsSkillXXL Lysimeters|/Users/numberone/XXL Lysimeter
The name shows up in the Claude app. Adding an agent means adding a line and running start-agents.sh. Removing one means deleting the line and killing its tmux window.
The core of the loop looks like this:
while true; do if [[ -n $TRANSCRIPTS/*.jsonl(#qN[1]) ]]; then claude --dangerously-skip-permissions --remote-control "$NAME" --continue else claude --dangerously-skip-permissions --remote-control "$NAME" fi echo "$(date -Iseconds) exit rc=$?, restart in 30s" >> "$LOG" sleep 30done
The if deserves an explanation. claude --continue in a folder without any previous conversation does not start a new session. It prints “No conversation found to continue” and exits with code 1. My first plan was “if it exits within 10 seconds, start fresh”. Claude Code talked me out of it: a quick crash during a network outage would also exit within 10 seconds, and then the agent would silently start a new, empty conversation. Instead, the script checks whether Claude has stored a transcript for this folder (they live in ~/.claude/projects/ under the folder path with every special character replaced by a dash). Deterministic, no guessing.
What I tested, and what happened
Let’s look at the results. All tests ran on the live fleet with six agents (the seventh joined later, see below).
Killing a single agent: I killed Remy’s claude process with kill -9. The log shows exit rc=137, and 30 seconds later the agent was back, with the same Remote Control URL and the same conversation. On my phone I saw nothing except a short gap.
Killing tmux completely: tmux kill-server takes down all agents at once. I did not trigger the LaunchAgent manually but waited for the 5-minute watchdog. After 179 seconds all six windows were back, again with unchanged session URLs. This is the property I care about most: whatever dies, the conversation and the URL in my app stay the same.
Running the start script twice: No duplicate windows, no duplicate sessions. Idempotent as intended.
A real reboot: I restarted the Mac at 19:08. At 19:09 the LaunchAgent had started all seven agents, all with Remote Control active and their previous conversations loaded. The error log was empty.
What didn’t work
The long-lived login token does not work with Remote Control. This was my biggest hope. Claude Code has a claude setup-token command that creates a token valid for a year, so you never have to log in again. I stored the token in the macOS keychain and started a test session with it. Claude started fine, but Remote Control refused with the message that it “requires a full-scope login token”, because long-lived tokens are limited to inference. So every few weeks Claude tells me “Your login expires in 3 days”, and I have to run /login once by hand and restart the fleet. Very disappointing, but understandable from a security standpoint: a token that can only call the model is much less dangerous when it leaks than a token that can remote-control my Mac.
How I operate it day to day
Everything I need fits in a short runbook:
~/agents/status.shshows a table: tmux window yes/no,claudeprocess yes/no, and the last three restarts from the log.tmux attach -t agentslets me look into any agent live.Ctrl-b wpicks a window,Ctrl-b ddetaches. Never type/exitin there, or the agent restarts after 30 seconds (the conversation survives anyway).- When the login expires: run
claude, then/login, open the URL on the phone, paste the code, thentmux kill-server && ~/agents/start-agents.sh.
I reach the Mac via VPN and macOS screen sharing. I left SSH switched off, because I didn’t need it.
Three insights
- The boring tools are enough. A loop, tmux and a LaunchAgent cover crashes, a dead terminal server and a reboot. In two weeks I never wished for a process manager.
--continueplus Remote Control is what makes this feel like magic. Because a restarted agent comes back with the same URL and the same conversation, crashes are invisible from my phone.- The weakest link is authentication, not process management. The manual re-login every few weeks is the one thing this setup cannot do alone, and the token workaround is blocked by design.