Bhavana AI

AI/ML insights

How I Set Up Claude Code to Run From My Phone (And What I Learned About tmux Along the Way)

I’ve been spending more time with Claude Code lately, and one thing kept bugging me: I’d start a session on my MacBook, get Claude working on something, and then need to leave. Maybe I’m heading to the couch, maybe I’m walking out the door. Either way, my coding session is stuck on a laptop I just closed.

What if I could just pull out my phone and pick up where I left off?

This turned into a surprisingly interesting rabbit hole. I explored everything from purpose-built iOS apps to SSH fundamentals, learned more about tmux than I expected, and ultimately landed on a setup that’s simple, reliable, and works every time. Here’s how I got there.

The options I considered

I started by surveying what exists. The landscape for “control Claude Code from your phone” is broader than you’d think.

There are purpose-built apps like Happy (an open-source mobile client that wraps Claude Code with end-to-end encryption), Claude Remote (which tunnels through Cloudflare), and CodeRemote (a web interface over Tailscale at $49/month). Anthropic themselves now offer Claude Code on the web through code.claude.com, where sessions run in cloud sandboxes connected to your GitHub repos.

These are all reasonable options, but they share a common property: they add a dependency. A relay server, a subscription, a third-party process sitting between you and your code. If their server goes down or they ship a buggy update, you’re stuck. I wanted something that would work every time, built on components I control and understand.

That pointed me toward the fundamentals: SSH, tmux, and mosh.

The foundation: Tailscale

Before anything else, you need your Mac and your phone to be able to talk to each other from anywhere. At home they’re on the same WiFi, but that doesn’t help when you’re on cellular or at a coffee shop.

Tailscale solves this. It creates a private WireGuard mesh network between your devices. Install it on your Mac and your iPhone, sign in with the same account, and they can reach each other from anywhere in the world. Your Mac gets a stable IP address (mine is 100.79.64.52) that works regardless of what network either device is on. It’s free for personal use and genuinely just works. I already had it installed, which saved me a step.

You also need Remote Login (SSH) enabled on your Mac. That’s in System Settings, under General, then Sharing. Toggle Remote Login on and you can SSH into your Mac from any device on your Tailscale network.

SSH vs mosh: why your phone kills SSH

SSH works great from a laptop. You open a connection, it stays open, life is good. Phones are a different story. Your phone goes to sleep constantly. It switches between WiFi and cellular. You walk through a tunnel. Every one of these events kills an SSH connection because SSH maintains a persistent TCP connection, and when the underlying network changes, TCP doesn’t recover.

Mosh (Mobile Shell) was built specifically for this problem. Instead of maintaining a persistent connection, it synchronizes terminal state over UDP. There’s no connection to “drop.” When your phone wakes up or switches networks, mosh just resumes. On networks with 29% packet loss, mosh’s response time is 0.33 seconds compared to SSH’s 16.8 seconds. It even shows your keystrokes instantly with an underline to indicate they haven’t been confirmed by the server yet, so typing on a bad connection doesn’t feel laggy.

Installing mosh is one line: brew install mosh. But getting it to actually work over SSH was where I hit my first wall.

The mosh-server PATH problem

When you run mosh user@host, mosh first SSHes into the remote machine and tries to launch mosh-server. On my Mac, Homebrew installed mosh-server to /opt/homebrew/bin/mosh-server. But here’s the thing: when SSH starts a non-interactive session, it doesn’t load your shell profile. The PATH it uses is the one compiled into sshd itself, which on macOS is just /usr/bin:/bin:/usr/sbin:/sbin. No /opt/homebrew/bin. So mosh SSHes in, tries to run mosh-server, and the SSH session has no idea where to find it.

My first attempt was symlinking mosh-server to /usr/local/bin, but sshd’s compiled PATH doesn’t include that either. Symlinking to /usr/bin would work, but macOS System Integrity Protection blocks writes to /usr/bin entirely.

The solution was to tell SSH to set up the right PATH when users log in. I created a file at ~/.ssh/environment with the full PATH including Homebrew’s directory, then enabled PermitUserEnvironment in the SSH daemon config so it would actually read that file. A quick restart of the SSH daemon with sudo launchctl kickstart -k system/com.openssh.sshd, and mosh connected on the first try.

The macOS firewall gotcha

Before the PATH issue, I hit an even more basic problem: mosh couldn’t connect at all. SSH worked fine, but mosh timed out. The issue was the macOS firewall blocking mosh’s UDP ports (60000-61000). Since I’m behind Tailscale (which is already a secure private network), the fix was straightforward. I added mosh-server to the firewall’s allowlist:

sudo /usr/libexec/ApplicationFirewall/socketfilterfw --add /opt/homebrew/bin/mosh-server
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --unblockapp /opt/homebrew/bin/mosh-server

If you’re not behind a VPN like Tailscale, you’d want to be more careful about which ports you open.

Enter tmux

At this point I could mosh into my Mac from my phone. But if I started Claude Code on my Mac and then connected from my phone, I’d get a fresh shell, not the session I was already working in. That’s the problem tmux solves.

tmux (terminal multiplexer) is a program that runs on your Mac and manages terminal sessions independently of any connection. When you start a tmux session, it creates a server process. Your shell runs inside that server, not inside your terminal app. If you disconnect (close your laptop, lose your connection, switch devices), the server keeps running. You reconnect and reattach to the same session.

I’ll be honest: tmux and I had a rocky start. The scrolling didn’t work (tmux intercepts scroll events, so you need to enter “copy mode” or enable mouse support). I kept connecting to the same session when I wanted multiple sessions (you need to name them). The key bindings felt alien (everything is prefixed with Ctrl+B). It felt like a tool fighting me at every turn.

But once I understood the mental model, it clicked. tmux is not a better terminal. It’s a server that manages terminal sessions. Your terminal app (Ghostty on my Mac, Moshi on my phone) is just a client that connects to that server. The session exists independently of any client. That’s why it survives disconnects, and that’s why you can attach to the same session from different devices.

What I actually set up

I added a shell function called cc to my .zshrc that wraps the common operations:

cc() {
  # cc kill <name|all> → kill sessions
  # cc (no args) → list sessions
  # cc <name> → attach or create session with claude
  # cc <name> resume → create session with claude --resume
}

cc blog creates a tmux session named “blog” and starts Claude Code inside it. If the session already exists, it attaches to it instead. cc api creates a separate session for a different project. cc with no arguments lists all active sessions.

My .tmux.conf is minimal:

set -g mouse on
set -g history-limit 50000

Mouse mode fixes the scrolling issue (you can swipe to scroll in Moshi), and the extended history buffer means I can scroll back through long Claude Code sessions without losing context.

The daily workflow

Here’s what my day actually looks like now.

On my Mac in Ghostty, I run cc blog to start working on a blog post with Claude. Later, I run cc api in another terminal to start a second agent working on an API project. When I need to leave, I just close the laptop.

From my phone, I open Moshi, connect with mosh varunr@100.79.64.52, and type cc blog. I’m immediately back in my blog session, exactly where I left off. I open a second tab in Moshi, connect again, and type cc api. Two agents, two tabs, swipeable on my phone.

If a session ended (maybe I exited Claude or the conversation wrapped up), cc blog resume starts a new Claude session with --resume, which pulls up my previous conversation history so I can pick up the thread.

When you don’t need tmux

One thing I realized during this process: for most interactive Claude Code usage, you might not need tmux at all. Claude Code’s --resume flag restores your full conversation history (every message, every file edit, every decision). If your workflow is “chat with Claude, leave, come back later,” then just moshing in and running claude --resume gives you continuity without any tmux complexity.

Where tmux becomes essential is when Claude is actively working on something. If you kick off a long autonomous task (“refactor this entire module” or “write tests for all these endpoints”) and walk away, tmux is the only thing keeping that process alive. Without it, closing the connection kills the running process. With it, Claude keeps working and you check in later from your phone.

The pieces

For anyone who wants to replicate this, here’s the full stack:

On your Mac: Tailscale (networking), mosh (resilient connection), tmux (session persistence), Claude Code (the actual thing you’re using). Enable Remote Login in System Settings and add /opt/homebrew/bin to ~/.ssh/environment so mosh-server is findable.

On your iPhone: Tailscale (same account as your Mac), Moshi (iOS terminal app with native mosh support, push notifications, and on-device voice input).

The connection: mosh user@<tailscale-ip> from Moshi, then cc <session-name> to attach to or create a Claude Code session.

The whole setup took about an hour, including the debugging. Every component is mature, independently maintained infrastructure software. There’s no relay server to go down, no subscription to pay, and no third-party process sitting between you and your code. It just works.

Sources

  • Tailscale — WireGuard-based mesh VPN for device connectivity
  • Mosh (Mobile Shell) — UDP-based remote terminal that survives network changes
  • tmux — terminal multiplexer for persistent sessions
  • Moshi iOS app — iOS terminal with native mosh support
  • Claude Code — Anthropic’s CLI coding agent