Skip to content
← Back to Blog

My Dev Environment in the Vibe Coding Era: Reachable, Not Just Fast 🛰️

(updated October 10, 2026) · 10 min read
toolingaitailscalesshtmuxclaude-codeproductivity

A few months ago I wrote about the keyboard-first setup that removed the mouse from most of my day.

That setup is still here. But the shape of my work has changed underneath it.

I type less code than I used to. I spend more time describing intent, reviewing diffs, approving (or rejecting) plans, and nudging an agent back on track. A lot of the “work” now happens while I’m not looking: a long refactor, a test run, a migration being drafted.

That changes what I want from a dev environment.

It used to be about speed at the desk. Now it’s mostly about reachability from anywhere.

If an agent is halfway through a task and needs one decision from me, I don’t want that to wait until I’m back at my desk. I want to answer it from my phone, in a queue, in about 30 seconds, and go back to my day.

This article is the stack that makes that possible:

  • Tailscale: one private network for all my devices
  • SSH + tmux: the terminal is still the main interface, from any device
  • Claude Code Remote Control: a nicer UI for steering sessions from my phone
  • AnyDesk / TeamViewer: the escape hatch when something needs a GUI
  • a few small dotfile tweaks that make the whole thing feel seamless

The philosophy: the machine works, I check in

Vibe coding isn’t (for me) “let the AI write everything and hope.” It’s closer to managing a very fast, very literal junior engineer who never gets tired.

Agents are good at grinding. They’re bad at knowing when they’re wrong.

So the bottleneck moves. It’s no longer how fast I can type. It’s how quickly I can:

  • see what an agent is doing
  • unblock it when it’s waiting on me
  • stop it when it’s going the wrong way

All three are latency problems. And latency is mostly an access problem.

My Mac stays home, plugged in, always on. Every other device I own is just a window into it.

Tailscale: the network I stopped thinking about

Tailscale (tailscale.com) is the foundation. It puts my Mac, my servers, and my phone on one private network (a “tailnet”) using WireGuard under the hood.

What it removed:

  • no port forwarding on the home router
  • no public SSH port getting hammered by bots
  • no dynamic DNS
  • no “which IP is it today?”

Every device gets a stable name. From anywhere (home, office, mobile data, hotel Wi-Fi), ssh ryeng-home just works.

The best infrastructure is the kind you forget exists. I installed Tailscale once, and I haven’t thought about networking since. That’s the highest praise I can give a tool.

SSH + tmux: the terminal is still the center

I still live in the terminal. The vibe coding era didn’t change that; if anything, it made it stronger. Agents like Claude Code are terminal-native, so a terminal is all I need to reach them.

On the phone side I use Termius (termius.com) to SSH into the Mac over Tailscale.

One tmux session per device

I don’t only reach the home Mac from my phone anymore. These days it’s at least three devices: Kitty on the Mac itself, Termius on my phone, and my work MacBook. More will come.

The trick that made this pleasant: every device gets its own tmux session.

  • main: Kitty on the home Mac auto-attaches here
  • mobile: SSH from my phone attaches here, and it starts in ~/projects with Claude already running
  • one session per extra Mac: SSH from another Mac gets a session named after that device, so ryeng-work lands in work

The reason is simple. If two devices attach to the same session, they fight over the window size. The phone shrinks everything to a tiny width, and when I get back to my desk the layout is a mess. One session per device means no device steals another’s screen, and each one picks up exactly where it left off.

How does the Mac know who’s connecting? Tailscale again. $SSH_CONNECTION holds the client’s tailnet IP, and tailscale whois turns that IP into a device name. There’s no list of devices to keep in sync: a new laptop that joins the tailnet gets its own session the first time it connects.

The relevant bit of my .zshrc:

tmux_ensure_mobile() {
  tmux has-session -t mobile 2>/dev/null && return 0
  tmux new-session -d -s mobile -c "$HOME/projects"
  tmux send-keys -t mobile 'claude' Enter
}

# Tailscale device name of the SSH client (e.g. ryeng-phone, ryeng-work)
ssh_peer_name() {
  command_exists tailscale || return 0
  tailscale whois "${SSH_CONNECTION%% *}" 2>/dev/null \
    | awk '/^  Name:/ { split($2, parts, "."); print parts[1]; exit }'
}

if [[ -z "$TMUX" ]] && command_exists tmux; then
  if [[ -n "$SSH_CONNECTION" ]]; then
    ssh_peer="$(ssh_peer_name)"
    if [[ -n "$ssh_peer" && "$ssh_peer" != *phone* ]]; then
      # Another Mac -> its own session (ryeng-work -> "work")
      tmux new-session -A -s "${ssh_peer#ryeng-}" -c "$HOME/projects" && exit
    else
      # Phone, or a client Tailscale doesn't know -> mobile
      tmux_ensure_mobile && tmux attach-session -t mobile && exit
    fi
    unset ssh_peer
  elif [[ -n "$KITTY_WINDOW_ID" ]]; then
    tmux_ensure_mobile
    tmux new-session -A -s main
  fi
fi

A few small details I care about:

  • claude is typed into the shell, not used as the session command. If I quit Claude, I land in a normal shell instead of a dead session.
  • Unknown clients fall back to mobile. If the connection doesn’t come through Tailscale (say, a plain LAN IP), whois returns nothing and I still get a sensible session.
  • && exit. Detaching from tmux also ends the SSH login, so I never end up in a stray shell outside tmux. But if tmux itself fails, the exit is skipped and I get a plain shell instead of being kicked out. That matters a lot when SSH is my only way in.

So from my phone: open Termius, tap the host, and I’m already looking at a Claude prompt in my projects folder. From the work Mac: ssh ryeng-home, and I’m back in the work session exactly as I left it. No setup, no tmux attach, no cd.

tmux is also the reconnect story

Mobile connections drop. You switch from Wi-Fi to 4G, the app gets backgrounded, the train goes into a tunnel.

With tmux, that doesn’t matter. The session (and whatever the agent is doing inside it) keeps running on the Mac. Reconnect, and I’m exactly where I left off.

Small things that make SSH feel local

A few tweaks that remove the “I’m on a remote machine” feeling:

  • SSH multiplexing. ControlMaster auto + ControlPersist 600 in ~/.ssh/config, so the second, third, and tenth connection to the same host are instant.
  • Clipboard over SSH. Neovim detects when it runs over SSH and switches its clipboard to OSC 52, so yanking text on the remote machine lands on the clipboard of whatever device I’m holding.
  • Tunnels on demand. My kubectl is a small shell wrapper that opens an SSH tunnel to the k3s API on my server if one isn’t already running. I never think about it; kubectl get pods just works.
local is_ssh = vim.env.SSH_CONNECTION ~= nil or vim.env.SSH_TTY ~= nil

if is_ssh then
  -- Remote: OSC 52 → local clipboard
  vim.g.clipboard = {
    name = "OSC 52",
    copy = {
      ["+"] = require("vim.ui.clipboard.osc52").copy("+"),
      ["*"] = require("vim.ui.clipboard.osc52").copy("*"),
    },
    paste = {
      ["+"] = require("vim.ui.clipboard.osc52").paste("+"),
      ["*"] = require("vim.ui.clipboard.osc52").paste("*"),
    },
  }
end

None of this is fancy. Each one just removes a small “ugh” moment, and those add up.

Claude Code Remote Control: steering without a terminal

SSH from a phone works, but let’s be honest: a terminal on a 6-inch screen is not a great place to read a 400-line diff.

That’s where Claude Code’s Remote Control comes in (docs). You start a session on your own machine:

claude remote-control --name "jms-go refactor"

…and it shows up in claude.ai/code and the Claude mobile app. The session still runs locally, with my files, my tools, my MCP servers, and my credentials. The phone is just a remote for it.

What I use it for:

  • approving or rejecting a plan before the agent starts editing
  • answering “should I do A or B?” questions
  • reading diffs and summaries in a UI that was actually designed for a phone
  • kicking off a follow-up task when the first one finishes

SSH vs Remote Control: when I use which

They overlap, but they’re good at different things:

SituationI reach for
An agent is waiting on a decisionRemote Control
Reading diffs, plans, and summariesRemote Control
I need a real shell (logs, git, restarting services)SSH + tmux
Something outside Claude needs fixingSSH + tmux
A GUI app or a system dialog is in the wayAnyDesk / TeamViewer

Rule of thumb: Remote Control for steering, SSH for doing.

AnyDesk / TeamViewer: the escape hatch

Not everything lives in a terminal. Sometimes the thing blocking me is:

  • a macOS permission dialog (“Terminal would like to control…”)
  • a browser login or OAuth flow that has to happen on the Mac itself
  • the iOS Simulator, Xcode, or an app I need to actually look at
  • a Keychain prompt
  • or the worst one: SSH itself is broken, and I need to see the screen to fix it

For those moments I keep a full remote desktop: AnyDesk (anydesk.com) as my main one, with TeamViewer (teamviewer.com) as a backup.

Do I use it often? No. Maybe a few times a month.

That’s exactly the point. An escape hatch isn’t supposed to be comfortable. It’s supposed to exist. Knowing that any problem on the Mac is fixable from my phone is what lets me trust the rest of the setup enough to walk away from my desk.

Also worth saying: remote desktop tools are a big attack surface. Use strong unattended-access passwords, turn on 2FA, and keep the allowed device list short.

The layers, from most to least used

Put together, it’s a stack of fallbacks:

  1. At the desk: Kitty + tmux main + Neovim + Claude in a tmux popup (same as before)
  2. Away, quick decision: Claude Remote Control in the mobile app
  3. Away, real work: Termius → Tailscale → SSH → tmux mobile (or, from the work Mac, its own work session)
  4. Away, something’s broken or needs a GUI: AnyDesk / TeamViewer

Each layer covers what the one above can’t. And because they all point at the same machine with the same state, switching between them costs almost nothing.

The hidden downsides

This setup has real costs, too:

  • An always-on machine. The Mac needs power, a stable connection, and sleep disabled. If the house loses power, the whole thing is gone.
  • Security is on you. More ways in means more doors to lock. Tailscale ACLs, SSH keys only, 2FA on everything, and be careful about what permission mode you let agents run with when nobody is watching.
  • The “always reachable” trap. Being able to unblock an agent from anywhere quickly turns into feeling like you should. I try to treat the phone as a way to save time, not a reason to never stop working.

And the same trap from my last post still applies: you can spend more time building the setup than using it. Tooling has to earn its keep.

My configs

Enjoyed this post?


Comments