Back to list

Blog

Why We Built Tactic Remote on tmux: Session Persistence and Terminal Architecture

Tactic Remote uses tmux to manage Claude Code sessions, providing persistence across network disconnections, Mac sleep cycles, and app restarts while enabling real-time terminal output streaming to your iPhone.

Published Tags: architecture / tmux / reliability / engineering
Blog

Follow product and engineering updates from this channel.

Browse category

When we started building Tactic Remote, the first architectural decision we faced was fundamental: how do you keep a Claude Code session alive and observable when the controlling device (your iPhone) has an unreliable connection? The answer was tmux, and that decision shaped nearly everything else about the product.

The Problem

Claude Code runs as a long-lived interactive process. A typical session lasts 10 to 60 minutes. During that time, Claude reads files, proposes changes, executes commands, waits for approvals, and iterates on feedback. If the process dies mid-session, all context is lost. Claude has to start over, re-read files, and reconstruct its understanding of the task.

On a local terminal, this isn't a problem — your Mac stays on and the process runs until it's done. But Tactic Remote introduces a mobile client. iPhones switch between Wi-Fi and cellular. They lock screens and suspend background apps. Users walk through areas with no signal. Any architecture that ties the Claude Code process lifecycle to the iPhone's network connection is fundamentally broken.

We needed Claude Code to run independently of any client connection and for clients to attach and detach without affecting the running session.

Why tmux

tmux is a terminal multiplexer that has been in production use since 2007. It separates the concept of a terminal session from the terminal emulator displaying it. A tmux session runs as a server process on the host machine. Clients attach to view and interact with the session, and detach without affecting the running processes.

We evaluated several alternatives before committing to tmux:

screen — The original terminal multiplexer. Functional but less actively maintained, with a more limited scripting API. tmux's structured command interface made programmatic control significantly easier.

Custom PTY management — We prototyped a solution using raw pseudo-terminals with node-pty. It worked for basic cases but required us to reimplement session persistence, window management, and output buffering. We were building a worse tmux from scratch.

Container-based isolation — Running Claude Code in a Docker container with an attached PTY. This adds overhead, complicates file system access (Claude Code needs to read and write your actual project files), and introduces a layer of abstraction that creates more problems than it solves.

nohup / disown — Simple background process management. No output capture, no reattachment, no programmatic control. Inadequate for interactive sessions.

tmux gave us everything we needed out of the box: session persistence, programmatic pane control, output capture, and a stable, well-documented API. The tradeoff is that tmux must be installed on the developer's Mac, but brew install tmux is a one-time setup cost that we consider acceptable.

Architecture

Tactic Remote's tmux integration works across three layers:

Layer 1: Session Management

When you start a Claude Code session through Tactic Remote, the Mac companion creates a new tmux session with a unique identifier. Claude Code launches inside this session's primary pane.

tmux new-session -d -s claude-remote-{session-id} "claude-code --project /path/to/project"

The -d flag is critical — it creates the session in detached mode. No terminal emulator needs to be connected for the session to exist and run. Claude Code starts immediately, and the Mac companion monitors it from the background.

Each session gets a dedicated tmux session rather than a pane within a shared session. This provides clean isolation: session-specific environment variables, independent scroll buffers, and no risk of cross-session interference.

Layer 2: Output Capture and Streaming

The Mac companion continuously captures the terminal output from the tmux pane and streams it to connected iPhone clients via WebSocket. This is the mechanism that lets you see Claude Code's output in real time on your phone.

Output capture uses tmux capture-pane with coordinates that cover the visible pane content plus the scroll buffer. The companion runs a capture loop that:

  1. Captures the current pane content at a configurable interval (default: 200ms).
  2. Diffs the captured content against the last known state.
  3. Sends only the delta to connected clients.
  4. Maintains a ring buffer of recent output for clients that reconnect and need to catch up.

The 200ms capture interval represents a balance between responsiveness and CPU usage. At this rate, terminal updates feel near-real-time on the iPhone while consuming less than 2% CPU on a modern Mac. Users who prefer faster updates can reduce the interval to 100ms; those running on battery power can increase it to 500ms.

Layer 3: Input Routing

When you send input from the iPhone — whether it's an approval response, a typed message, or a session control command — the Mac companion routes it to the appropriate tmux pane using tmux send-keys. This maintains the illusion of a direct terminal connection while the actual input travels through WebSocket, across the network, and into the tmux session.

Approval responses are a special case. Rather than being sent as raw keystrokes, they're processed by the hook system and injected as structured responses that Claude Code's permission system expects. This ensures approvals are atomic and can't be corrupted by partial network delivery.

Persistence in Practice

The tmux architecture provides persistence at several levels:

iPhone app closes. The tmux session continues running. Claude Code keeps working, and approvals queue on the Mac companion. When you reopen the iPhone app, it reconnects to the WebSocket, receives the buffered output, and you're back where you were.

iPhone loses network. Identical to the app closing from the session's perspective. The WebSocket drops, output buffers on the Mac, and reconnection restores state.

Mac goes to sleep. tmux sessions survive sleep and wake cycles. When the Mac wakes, the tmux server process resumes, and any Claude Code process that was mid-execution continues. The Mac companion re-establishes the WebSocket listener and reconnects to the iPhone.

Mac companion crashes. The tmux session survives independently. Restarting the Mac companion detects existing tmux sessions with the claude-remote- prefix, reattaches to them, and resumes streaming. No Claude Code context is lost.

Mac reboots. This is the one scenario where tmux sessions do not survive, because tmux sessions exist only in memory. Claude Code must be restarted. We document this clearly and are exploring tmux-resurrect integration for future versions, though the complexity of restoring interactive AI sessions (not just shell state) makes this nontrivial.

Session Lifecycle

A Tactic Remote session goes through these states:

Statetmux StatusiPhone Status
StartingSession created, Claude Code launchingLoading spinner
ActiveProcess running, output flowingLive terminal view
Waiting for ApprovalProcess blocked on hookApproval notification
Paused (client disconnected)Running, output bufferingNot connected
CompletedProcess exited, session retainedSession summary
ExpiredSession destroyed after retention periodArchived

Completed sessions are retained in tmux for a configurable period (default: 30 minutes) so you can review the final output even after Claude Code exits. After the retention period, the tmux session is destroyed and the output log is archived.

Observability

tmux gives us introspection capabilities that would be difficult to achieve with a custom PTY solution:

  • Session listing — tmux list-sessions provides an instant inventory of all active Tactic Remote sessions.
  • Pane dimensions — We can query and set pane dimensions to match the iPhone's display characteristics, ensuring output wrapping is correct.
  • Process monitoring — The tmux list-panes command with format flags lets us monitor the Claude Code process without maintaining a separate PID tracking system.
  • Scroll buffer access — The full scroll buffer is available for search and retrieval, which powers the session history view on the iPhone.

Performance Characteristics

Running through tmux adds minimal overhead compared to a direct terminal:

MetricDirect Terminaltmux Session
Process startup~800ms~850ms
Keystroke latency<1ms~2ms
Memory overhead per session—~3MB
CPU overhead (idle session)—<0.1%

The overhead is negligible in typical workflows. tmux has been optimized over nearly two decades of production use, and the additional indirection is usually not noticeable to users.

Lessons Learned

Building on tmux was the right call, but it wasn't without challenges:

Terminal encoding edge cases. Claude Code's output occasionally includes ANSI escape sequences that interact unexpectedly with tmux's terminal emulation. We maintain a normalization layer that cleans output before streaming to the iPhone.

macOS tmux installation. tmux isn't pre-installed on macOS. We considered bundling it but decided against it — developers generally prefer managing their own tmux installation, and bundling would create version conflicts for users who already have tmux installed.

Capture-pane performance at scale. With many concurrent sessions, the capture loop's CPU usage scales linearly. We implemented adaptive capture rates that reduce frequency for sessions with no recent output changes.

tmux is infrastructure that disappears when it works well. Most Tactic Remote users don't know or care that tmux is involved — they see a reliable session that survives whatever their phone or network does. That's the outcome we were aiming for.

Try Tactic Remote

Control your coding Agents from your phone

Connect to Claude Code, Codex, and other Agents on your Mac, Windows, or Linux computer. Check progress and send the next instruction from iPhone or iPad.