# Agentmux iOS, iPadOS and Android client for SSH, persistent tmux sessions, and AI coding agents such as Claude Code, Codex, Gemini and OpenCode. Source: https://agentmux.saxcave.cc/ | App Store: https://apps.apple.com/app/id6766158521 | Google Play: https://play.google.com/store/apps/details?id=cc.saxcave.agentmux --- # Guide — English ## Your AI should be working while you sleep URL: https://agentmux.saxcave.cc/guide/en/why-remote-vibe-coding/ Language: en Updated: 2026-11-06 The cost of running agents remotely is standby: the agent waits on you. tmux and SSH move that wait to your phone. A CLI agent's real cost is standby, not typing: thirty seconds of input, then hours you cannot leave. Run the agent on a machine that stays on, keep its session alive with tmux, and reattach over SSH and Tailscale. Then standby costs a phone and fifteen seconds, and the agent keeps working while you sleep. You have probably noticed it: the agent does the work, and you are the one tied down. That tie feels like a discipline problem, a question of how well you can step away. It is a physical problem. One terminal window on one laptop has to stay open. ## Where does the time go? A round of work with a CLI agent almost always has the same rhythm: ``` You talk for 30 seconds → it runs for 5–20 minutes → it needs you to decide something → repeat ``` Lay a whole day out in those proportions and typing is not what eats it. Those 30 seconds are the shortest part. Everything after them is waiting: it reads the project, it edits a dozen files, the test suite runs. Then it stops to ask whether it should handle that migration while it is already in the code. You answer in one line, then wait another five or twenty minutes. By the end of the day you have produced maybe ten minutes of typing and lost several hours. Those hours were **standby**: you did nothing, and you could not leave, because at any moment it needed one sentence from you. ## The usual setup nails standby to one laptop That standby has a physical location. The agent runs in a terminal window on your laptop. The window is tied to a shell, and the shell to one machine that has to stay awake. You then have two options, and neither is cheap: **Sit and wait.** Stay in the chair, watching a screen where nothing happens, so that you can answer within thirty seconds when it asks. An hour of your time buys three replies. **Or close the laptop.** The process suspends or dies, the session goes, and everything the agent had built up goes with it: which files it read, how far it got, why it chose that approach. Come back later and you open a new window, explain the task from the beginning, and start again. Either option costs the same, and the cause is **where that machine sits**. The work itself never needed you in the room. ## What three tools undo the tie? The tie has three layers, and each layer has a standard tool that has been around for decades. Any one of them looks ordinary. Stacked in the right order, they do the whole job: | Piece | The tie it undoes | | --- | --- | | CLI | The agent is a plain-text process that needs no GUI → it can live on another machine | | tmux | The session is pinned to the server and survives a dropped connection → standby doesn't need you present | | SSH + Tailscale | Reattach securely from any network, with no public port and no static IP → standby isn't tied to a device or a place | The order is not arbitrary. The CLI comes first: only then can the agent leave your laptop for the Mac mini at home that never gets switched off. tmux comes next, so the session is pinned to that machine and keeps running after your connection drops. SSH and Tailscale come last, so you can reattach to that same session from the train, the office, or someone else's Wi-Fi, without opening a single outward port on your router. Drop any one of the three and it falls apart. CLI without tmux: the agent dies when the connection drops. tmux without a secure way in: you can only use it at home. ## Standby costs a phone and fifteen seconds Once all three are in place, checking in means unlocking your phone for fifteen seconds instead of finding a desk, a seat, and an outlet. Fifteen seconds changes what the activity is. You stop setting aside a block of time to babysit it. You glance at it in a queue, at a crossing, before bed, reply "use the second approach", and put the phone away. When you look again the agent is still working, and it has moved on without you. Once standby stops requiring you, the work moves into the hours you were never going to spend at a desk anyway. Hand it a refactor and go to dinner. Start a test-fixing session before bed, and read the results over breakfast. The machine that used to sit idle every night is now the one doing the work. ## What else reaches your agent when you're away? You have more than one way to reach your own agent from outside. Line the options up against the same criteria and the differences are plain. The last column is the one that matters: it is where people find out they are stuck. | Approach | Cost of standby | Does context survive | Network setup needed | Can it really edit your files | | --- | --- | --- | --- | --- | | Carry the laptop | You need a bag, a seat, and power | Stops when you close the lid | None, but the machine has to travel with you | Yes, the files are local | | VNC remote desktop | Needs a big enough screen, and a desktop UI driven by a finger | Survives as long as the desktop session does | Punch through for VNC, or stand up a VPN | Yes | | Browser-based agent | Low: just open a browser | Lives on someone else's server | None | It edits a copy in a cloud sandbox, not the one on your machine | | Cloud environments like Codespaces | Low, but you need a browser | The container gets reclaimed after enough idle time | None, but the project goes to the cloud and you're billed by usage | Yes, but it's the repo inside the cloud container | | General SSH app | A phone is enough | Holds up, thanks to tmux | Securing the way in is on you | Yes, but the interface is a desktop terminal ported across as-is | | Agentmux | A phone, fifteen seconds to reattach | tmux is on the server, regardless of the phone | Tailscale, with no public port | Yes, the copy on your own machine | An agent in someone else's cloud sandbox edits a copy, always. Your `.env` is not in that sandbox. Neither is the internal package you installed this morning, or the database that only runs locally. Take the SSH and tmux road and the agent sits on your machine, in your environment, editing your files. The only thing that changed is that your phone is watching it. Not sure where to start? Help me pick a track. ## Why not Cursor or VS Code URL: https://agentmux.saxcave.cc/guide/en/why-not-gui-ide/ Language: en Updated: 2026-11-06 On a desktop the IDE agent usually wins. On a phone a terminal wins: a byte stream can be stored and reattached. An agent inside Cursor or VS Code runs as part of a GUI process, so it needs a screen, a logged-in desktop, and someone sitting in front of it. A CLI agent's interface is a stream of characters, which tmux can store whole and reattach on any device. The difference is architectural, not a matter of taste. You already have Cursor, or VS Code with an agent extension, and it works well. Learning SSH and tmux for the same job pays off in one situation: you are not sitting at the machine. ## The difference is architectural, not a preference An agent inside a GUI IDE is part of that GUI program. It lives in the application's process, its state sits in that window's memory, and its output is painted onto a canvas. That is why it needs a screen, a logged-in desktop session, and a person in front of it. Close the window and the agent ends with it. Nobody overlooked anything: the agent grew out of that process. A CLI agent's interface is a byte stream: characters in, characters out, and nothing graphical anywhere along the way. A stream like that can be **attached to, detached from, and attached again**. tmux does the bookkeeping, because an 80×24 matrix of characters can be stored whole and redrawn onto any screen: your laptop, your phone, a friend's iPad. Changing devices loses nothing, because what changed hands was only ever characters. That is the whole reason a CLI agent works remotely: **a canvas of pixels cannot be serialised and reattached, and a matrix of characters can**. It is a claim about capability, not about who prefers keyboards. ## What happens when you close the laptop mid-task? Suppose you hand the agent a large refactor at the end of the working day. It needs twenty minutes at least, and you are leaving now. | Cursor left open | tmux + Claude Code | | --- | --- | | Close the laptop | Agent stops | Agent keeps running | | Two hours later | Open it, context is gone, start again | Open your phone, it's finished | Nothing in the right-hand column depends on how clever the agent is. That session never lived on your laptop. It lives on the machine that was never switched off, and your laptop was attached to it for a while. Closing the lid ended the connection, and the work carried on without you. Cursor has cloud background agents now, and those do keep running after you close the laptop. What keeps running is a copy inside Cursor's own remote sandbox, not the agent on your machine, which puts them with the browser agents and Codespaces discussed elsewhere on this site. The left-hand column still describes what happens to the agent on your machine. ## "Couldn't you just use remote desktop?" This is the most common objection, and in theory it is airtight. The desktop is still running on that machine. VNC back into it and Cursor is still open, so the problem looks solved. In practice the two ship different things. **VNC sends pixels. SSH sends characters.** One full screen update is hundreds of kilobytes to a few megabytes of image data, and the same screen as characters is a few kilobytes of text, a gap of roughly three orders of magnitude. You hit that gap on ordinary days. On a train, in a lift, anywhere you are down to one bar, VNC turns into a slideshow and a single scroll takes three seconds to redraw. SSH characters arrive instantly on the same connection. Even with bandwidth to spare you are still facing a desktop UI built for a mouse and a 1440p display, crammed onto a six-inch touchscreen. Hitting a menu item means zoom, aim, tap. An interface nobody designed for touch does not get easier just because the connection is fast. ## At your desk the IDE is often the better tool An IDE is not the wrong tool, and at your desk it is often the better one: go-to-definition, refactoring, a debugger, none of which work over a phone connection. **An IDE suits "you are here and you are driving". A CLI agent plus remote access suits "it is running and you are checking in".** Use both if you can: the IDE on the Mac while you are at the desk, and Agentmux to rejoin the same agent on the same project once you have left it. Agentmux is not trying to replace your IDE. It replaces the part where you sit in a chair and wait. Then you put the phone in your pocket, the signal drops in the tunnel, and what happens to that session is a problem of its own. ## Why the strongest coding agents live in the terminal URL: https://agentmux.saxcave.cc/guide/en/what-is-cli/ Language: en Updated: 2026-11-06 Why coding agents live in a terminal: full filesystem access and permission to execute. What a shell is, and when a GUI tool wins. A CLI agent is a coding agent you talk to in text. It runs in a terminal on a machine you own, a Mac, a Linux box, or a server. There it inherits your permissions: read every file in the project, run any command, read the result back. Agents live in terminals because no other interface grants all three. "CLI" still reads like a punishment: no mouse, no buttons, one blinking cursor. Yet Claude Code, Codex, Gemini CLI and OpenCode all live there. Their makers are not purists. The terminal grants two things no other interface grants together: full access to your files, and permission to run commands. That same permission is why it asks you before running anything. ## What is a terminal, once you strip the buttons away? Two programs are stacked here, and beginners blame the wrong one. The **terminal** is the display layer: it draws a grid of characters and forwards your keystrokes. Behind it sits the **shell** (zsh, bash, fish), which waits for a line of text, runs it, and prints what that program wrote. That thinness is why an agent can drive a real project end to end. The commands it types are commands you would have typed, and the errors it reads are the errors you would have seen. ## Three things you read off a terminal screen Three parts of that screen carry the meaning. The rest is decoration. ``` yuki@mini ~/projects/auth (main) $ npm test ✓ src/session/refresh.test.ts (6 tests) ✗ src/session/expire.test.ts > drops a session after expiry Tests 1 failed | 41 passed (42) $ echo $? 1 ``` **The prompt** is that first line. It says who you are, which machine you are on, which directory, and which branch. That line stops being decoration once the machine is not in front of you. After an SSH login it is how you tell your laptop apart from a server you forgot you were still logged into. **Standard output** (*stdout*) is the text the program wrote. The second stream, *stderr*, keeps its complaints separate so the real output stays readable. **The exit status.** Every command leaves one integer behind, and the shell hands it to you as `$?`. Zero means the program claims success. An agent reads that number rather than the words "1 failed", and a `1` is its signal that the loop is not closed yet. ## The interface decides what an agent can reach A browser agent cannot see a single file on your computer. It lives inside a tab. An IDE plugin sees the files you have open but usually stops at suggestions: it cannot touch git, cannot run your tests, and cannot install packages. A CLI agent holds the permissions you hold. It reads the whole repo, runs `git commit`, runs the test suite, and installs dependencies. Access arrives in three tiers, and the risk climbs with each one: - **Read.** Every file in your home directory, including the keys in `.ssh`. - **Write.** Inside a git project, a bad edit is one `git checkout` away. Outside one, nothing rolls it back for you. - **Execute.** Anything you could type, including a migration, a force-push, or an `rm -rf` pointed at the wrong path. No vendor designed this gap to please anyone. It is the permission set that comes with the terminal, and every other interface gives away less of it. ## Which commands can it run that a sandboxed agent cannot? "Execution rights" is an abstraction. Here is the same thing as lines on a screen: ``` npm test npx vitest run src/session/expire.test.ts git status --short git commit -m "fix: expire sessions on the dot" npm install date-fns@3 npm run build psql "$DATABASE_URL" -f db/schema.sql curl -sf localhost:8000/health | jq .db ``` A hosted sandbox has nowhere to run those. That `psql` connects to a database that exists only on your machine, using a URL from a `.env` file that was never uploaded. That `npm install` pulls an internal package from a private registry named in your `.npmrc`. Those `git` commands run in a clone that carries your remote, your hooks, and your signing key. A sandboxed agent can hand you a patch, but it cannot observe what that patch did. An agent that cannot see the result of its own edit cannot finish the task. ## The loop that closes without you pressing anything You send one line: "fix this bug." A dozen commands of that kind follow, and your hands stay off the keyboard: - It greps for the failing message, reads three files, edits one. - It runs `npm test`, reads the exit status, sees `1`. - It narrows to one case, edits again, reruns the whole suite, gets `0`. - It commits, applies the migration locally, and curls the endpoint that was broken. Not one step in that chain needs a click. Put the same agent in a browser tab or an IDE plugin and it stalls at step two. It can produce the edit, but it cannot run the suite, so it waits for you to run the tests and tell it what happened. You have become the test runner. That is a permissions problem, not a model-quality one. ## Why is a byte stream worth more than a window? The program does not live inside the terminal window. It is attached to a **pseudo-terminal**, a kernel object with two ends. Bytes go in one end and come out the other, and your window is whoever happens to be holding the far end right now. Three things follow from that. - **It can be saved.** `npm test > build.log` writes that text to a file, which another program can read a year later. A GUI's canvas is a picture, and a picture is not a record. - **It can be chained.** `git ls-files | grep .env` feeds one program's output into the next program's input. - **It can be moved.** Nothing in the definition says the two ends have to be in the same place. tmux parks the session on the server, and your screen attaches, detaches, and attaches again from another device. The third one is the point of this guide. **A byte stream can be redirected, stored, replayed, and reattached from somewhere else. A rendered window cannot.** Persistent sessions, SSH, and the same agent on your phone all follow from that one property. ## What does full access cost you? The permission that lets an agent migrate your database is the permission that lets it delete one. That is why an agent stops mid-task and asks: "Run this command?" The prompt is not timidity. An agent that ran everything without asking would be deciding about your `.ssh` directory and your production database on your behalf, from guesswork. The prompt is where you draw the line between tier two and tier three, and it can only be drawn at that moment: the command is known, and it has not run yet. Two habits cover most of the risk. **Read the command, not the summary:** "I'll clean up the build cache" is a description, and only the line under it has consequences. **Approve a category once:** if it asks you to run tests forty times a day, write one rule that lets `npm test` through. A prompt you were going to approve anyway costs ten seconds. One you approve by mistake, against a live database, costs a day and a restore. That ratio is why AI Permission Review exists: a blacklist blocks what is obviously dangerous, an allowlist passes what you already trust, and a model judges the rest. ## When a GUI is the better tool A terminal is not the right interface for everything you do to code. These are strictly worse in text: - **A live debugger.** Set a breakpoint, step through the loop, inspect a variable at frame four. - **Anything visual.** Layout, spacing, a colour that is almost right. Asking an agent to describe a screenshot is worse than looking at one. - **Reading a large diff.** Two thousand lines side by side is why you review an agent's work in a GUI, even when the agent wrote it. - **Still learning.** An editor underlines the exact token where you went wrong, which teaches faster than a wall of stack trace. The line here is not skill against laziness. It is **"you are here and driving"** against **"it is running and you are checking in"**. At a desk an IDE is often the better instrument, and the people who get the most out of a CLI agent keep both. That division of labour is its own article. The terminal puts an agent on your machine, and its output is characters that can live anywhere. So that machine does not have to be the one you are sitting at. Getting there from somewhere else is SSH's job. ## SSH is a tunnel to a machine you own URL: https://agentmux.saxcave.cc/guide/en/what-is-ssh/ Language: en Updated: 2026-11-06 SSH is an encrypted channel to another computer: host, user and port, why keys beat passwords, and why no port faces the internet. SSH is an encrypted channel between your device and another computer. You send keystrokes, that machine runs the commands, its output comes back, and no hop in between can read any of it. To run a coding agent remotely, SSH is how your phone reaches the machine the agent runs on. The last article argued that an agent does its best work in a terminal, with full access to a filesystem and permission to execute. That terminal does not have to be in your pocket. It can be the Mac mini at home that never gets switched off, or the workstation at the office, as long as your phone can route to it. ## Why another machine instead of a cloud service? Browser agents and cloud IDEs connect you to a machine too. The difference is whose machine it is. SSH connects you to your own. Your files and API keys are already there, along with every package you installed and every environment variable you set. Nothing has to move onto someone else's server, and the changes stay where you made them, so there is nothing to sync back afterwards. The trade-off runs the other way too. A cloud environment is the better answer if you own no machine that stays on, or if a whole team needs one identical starting point. Your machine has to be awake, reachable, and set up by you. ## What you actually type: host, user, port Every SSH connection needs three things, and the command writes them on one line: ``` ssh -p 22 sax@mac-mini ``` | Part | What it names | | --- | --- | | Host | Which machine: a name such as mac-mini, or an address such as 100.101.102.103 | | User | Which account on it. You get its files, permissions, and shell, and nothing above that account | | Port | Which door. SSH's default is 22, so the field usually goes empty | Two of those three are where beginners get stuck. The host has to be an address your phone can route to; the next section covers which ones. And the user has to exist on that machine: an agent runs as that user, and it can touch exactly what that user can. ## Why not your LAN address or your public IP? The first address you find is in your network settings: something like `192.168.1.23`. Try it from a laptop on the same Wi-Fi; that is what it is for. It is a private address handed out by your router, valid only inside your own network. A phone on mobile data has no route to it. The second is the public address your home connection shows the internet, with a port forwarded through the router to that one machine. That road works, and it is the one to avoid. Scanning bots try logins against every address in the world, around the clock, and an exposed port starts collecting attempts within a day. One mistake in the router settings opens the whole network instead of one machine. Use a third kind: a private address only your own devices can see. Tailscale gives each machine a MagicDNS name or a `100.x` address, and nothing else on the internet knows those exist. That goes in the host field, at home and away. ## Keys are safer and need less typing SSH takes passwords or keys, and the choice gets framed as security versus convenience. Keys are better at both. A key pair is two small text files. The private one stays on the device that connects. The public one is computed from it and copied onto the machine you want to reach, into `authorized_keys` inside that account's `~/.ssh` directory. The arithmetic runs one way. The public half comes from the private half, and you cannot recover the private half from the public one. `ssh-keygen` makes a pair in seconds. At login the two halves prove the match and you type nothing. A password has to survive repeated guessing, and a key is long enough that guessing never pays. Set it up once and every connection afterwards works the same way. Put a passphrase on the private key, because a key is a file. Copy it off an unlocked laptop and someone else holds your key. The passphrase makes that copy worthless by itself, and unlike a login password it never crosses the network: it unlocks the key on the device, and what travels is a signed answer to a challenge. On a phone the difference matters more. A typed password goes over the wire on every connection, handing anyone who can reach the port a fresh guess. A key answers a challenge instead, and the private half never leaves the device. If the key is in place and still refused, the usual causes are few: wrong path, wrong user, wrong permissions. ## Where does the app keep my key? Agentmux collects no personal data. Usage statistics are anonymous and optional; turning them off changes nothing about how it connects. Passwords, keys, and passphrases go into the operating system's hardware-backed credential storage. Your SSH traffic is never logged: the channel is encrypted between your device and your machine, and the app is not a relay. ## Why the fingerprint prompt appears the first time The first time you reach a machine, before any password or key, the client prints a line about the host's authenticity, a short string of hex, and a yes or no. That string is the server's fingerprint: a readable summary of the host key the machine signs with. Nothing has gone wrong. SSH is authenticating the other direction. A password or key proves you to the machine. A host key proves the machine to you. Without that second half, anyone between your phone and your Mac mini could answer in its place and read what you send. On a first connection there is no stored copy to compare against, so the client asks you to accept it and remembers it from then on. It earns its keep later. Reconnect to a machine whose fingerprint has changed and the client refuses. Either you reinstalled the system, or something else is answering. ## Do you have to turn your computer into a server? People hear "SSH" and picture racks in a data centre. You are flipping a switch. On a Mac it is System Settings → General → Sharing → Remote Login. On Linux it is usually starting a service called `sshd`, which most distributions already ship. Nothing else about the machine changes. It is still your everyday computer, with one more door you can knock on from outside. If you would rather not remember the order of operations, the setup page asks three questions and hands back a checklist. ## SSH is not a VPN or a remote desktop Three things SSH does not do: | Not this | What that would mean | What SSH does instead | | --- | --- | --- | | A VPN | Everything your phone sends routed into your home network | One channel to one port on one machine. Nothing else becomes reachable | | A mounted filesystem | Your remote disk appears as a folder | Bytes on request. A file browser fetches and pushes each file itself | | Screen sharing | Pictures of a desktop, steered with a finger | Text both ways: keystrokes out, printed output back | That last row is a real limit, not a flaw in this tool. Software that exists only as a window you click cannot be driven over SSH, which is the same boundary the previous article drew: an agent that speaks text is an agent that fits down the tunnel. Then you put your phone away, the tunnel drops, and the agent dies with it. Unless the session was never living inside your connection: that is what tmux is for. ## Keep the agent running after you put your phone away URL: https://agentmux.saxcave.cc/guide/en/what-is-tmux/ Language: en Updated: 2026-11-06 tmux is a terminal multiplexer: the session lives on the server, so a dropped SSH detaches instead of killing the agent. tmux is a terminal multiplexer: it keeps a terminal session running on the machine you connect to, rather than inside the connection you came through. For an AI coding agent, that is the difference between surviving and dying. SSH connections drop when a phone locks or a signal goes, and a dropped SSH normally kills the agent's shell. You put your phone away, the SSH tunnel drops, and the agent dies with it. That is mechanics, not bad luck. When the connection goes, the kernel closes the pseudo-terminal your login was using. It sends **SIGHUP** (hangup) to every process attached to it: your shell, and the agent inside it. A process that does not catch that signal on purpose terminates by default, and the kernel throws away whatever was half-finished. SSH was only the path the connection came along. It was never what kept the agent alive. ## tmux lets the session live somewhere else tmux opens a session on the server, and your agent runs inside that session rather than inside the SSH connection that keeps dropping. The mechanism is a process. The first time you type `tmux`, it starts a background program called the tmux *server*, which stays alive under your user account. Sessions belong to that process, and each one gets its pseudo-terminal from it instead of from `sshd`. When the connection goes, `sshd` closes its own pseudo-terminal, and only your tmux client is attached to it. The client exits. The session keeps running. ## What is a session, a window, a pane? People use three nouns for three different things, and only the first one keeps your agent alive. | Word | What it is | What it means for an agent | | --- | --- | --- | | session | A named place to work on that machine, holding its own processes | The thing that survives. Name it once | | window | A tab inside a session; most people give each agent one | Switch away and it keeps running | | pane | A split inside a window: agent left, tests right | Two things visible on a phone screen | A fourth word: a **client** is the terminal looking at a session. A session with zero clients is normal. It is detached, still running, and nobody is watching it. Names matter. The session you made with `tmux new -s work` is still findable a week later, while an unnamed session is just a number you have to look up. ## Is detaching like turning off a television? Switch a TV off and the picture in your living room is gone. The broadcast never stopped. Turn it back on and you are on the same channel, wherever it has got to by now. tmux calls those two actions **detach** and **attach**: close the window, open it again. Neither one touches what is running on the machine. The analogy hides one thing, and it works in your favour. A television learns nothing while it sits switched off, but a tmux session keeps its output. Attach an hour later and you can read the hour you missed by scrolling back through it. ## Which commands do I type? Start with four: ``` tmux new -s work # make a session named work claude # start the agent inside it tmux ls # list sessions: work: 1 windows tmux attach -t work # look at it again, from anywhere ``` **Detach is a key press, not a command.** Press Ctrl-b, let go, then press d. That first key is the *prefix*: tmux takes it, and the next key says what to do. Two more are worth learning: - `Ctrl-b d` detaches. The terminal becomes an ordinary shell again, and the session keeps running. - `Ctrl-b c` makes a new window; `Ctrl-b %` splits a window into panes. If the machine answers `tmux: command not found`, the fix is on the troubleshooting page. ## A dropped connection does not interrupt the agent You lose signal on the train, again in a lift, again when you switch Wi-Fi. Attach to the same tmux session every time and you can drop ten times without interrupting the agent once. It never notices, because it was never living inside your connection. The three cases people worry about on a phone are one event described three ways: - **The phone locks.** The system suspends the app, the connection comes apart, and nothing reaches the agent. It keeps working. - **No signal for twenty minutes.** The connection dies quietly and the tmux client dies with it. The session stays detached and untouched. - **You kill the app or swipe it away.** A process on the phone is gone, and the session was never inside it. Close a laptop's terminal window and tmux behaves the same way. Detach does not pause anything. While you are gone the agent keeps reading files, editing them, and spending your quota. That is the point of it, and it is why you answer a prompt that is waiting before you walk off. ## Can two devices watch the same session? Yes, and there is no trick to it. Several clients can attach to one session at the same time. Start the job from your phone in a queue, attach from your laptop at work, and you are looking at the same screen mid-sentence rather than a copy of it. Keys from either device go to the same program, so only one person types at a time. Attaching moves nothing: the session stays on the machine that made it, which is why your phone and your laptop agree. A session you started by hand in a laptop terminal is there for the app too. ## How the app handles these sessions Agentmux is built on the same split. A disconnect on the phone's side is a detach and nothing more: reconnect and you are back at that session, further along. Type the commands above in its terminal and it is still the same session. The app takes two chores off your hands. If the machine has no tmux, the app installs it, working out whether your machine uses apt, yum, dnf, pacman, apk, or brew. If an agent sits where the shell can't see it, the app probes that PATH problem. Persistent sessions are in the free single-server tier, so nothing here is paid. The setup page turns it into a checklist. ## What tmux does not do tmux answers one question, whether a process outlives your connection, and leaves the rest alone. - **A reboot of the server ends every session.** They live in the tmux server's memory, so a power cut or an unattended upgrade ends the agent with them. What survives is what reached the disk: files changed, commits made, the branch you were left on. Git is the save file, not tmux. - **It does not undo a command the agent already ran.** tmux keeps the output of that command, but it does not roll the files back. - **`tmux kill-session -t work` really does kill it.** So does quitting the last window, or the agent crashing. `tmux kill-server` takes every session with it, and tmux does not ask first. - **It does not keep the machine awake.** A Mac mini that stays on is a different case from a laptop that sleeps when you shut the lid, and a machine that powers off loses the session. Checking this takes two minutes. SSH in, run `tmux new -s work`, start an agent, press Ctrl-b then d, close the connection, and run `tmux attach -t work`. The same screen comes back, further along, with one question waiting for you. So the agent survives you putting the phone away. One problem left: getting in from outside without opening a port to the whole internet. Tailscale answers that. ## Tailscale: no port open to the internet URL: https://agentmux.saxcave.cc/guide/en/what-is-tailscale/ Language: en Updated: 2026-11-06 Tailscale builds a WireGuard mesh between your own devices, so your phone connects to a MagicDNS name or a 100.x address and no port opens. Install Tailscale on your computer and on your phone, sign both in to the same account, then connect with SSH to the machine's MagicDNS name or its `100.x` address. Tailscale builds an encrypted WireGuard mesh between your own devices, so you open no port on your router to the internet and never point SSH at a public address. The agent survives a dropped connection, which only helps if there is somewhere safe to reconnect from. The obvious answer is to expose port 22 to the internet, and that is the fastest way into trouble. Scanning bots knock on port 22 across every IP on earth, around the clock. A second problem is duller and harder to dodge. Your home IP is dynamic, and the machine sits behind NAT, sharing one outward address with everything else at home. There was never a public address there to open. ## Three jobs on the router, all by hand The traditional fix meant the router itself: port forwarding so a public port reaches the machine, a DDNS service binding a hostname to today's IP or a static IP from your ISP, then firewall rules kept straight by hand. Get one step wrong and you cannot get in, or you have exposed your whole home network. Get them all right and a door still faces the internet. ## Why does port forwarding plus a dynamic IP keep breaking? Four parts have to hold at the same time, and any one of them gives way at an inconvenient moment. - **The address changes.** Your ISP reassigns it, and a DDNS client needs a few minutes to catch up. For a window after every change, nothing you saved points at home. If that update fails while you are away, the hostname belongs to somebody else. - **Sometimes the address is not yours.** Behind carrier-grade NAT your router holds an address inside the ISP's own network, so no forward can work. - **One forward is one more door** into a network shared with a smart TV and an old camera. - **It has to stay right forever.** A firmware update resets the rules; a new router means rebuilding them. The worst part is the debugging. You are on mobile data, trying to fix a home router from your phone, and the machine you want is unreachable. ## What Tailscale does Tailscale is a WireGuard mesh. Install it on each device, sign in to the same account, and the devices build an encrypted link straight between themselves. The setup opens no public entry point and leaves no port on the internet for a bot to scan. Each device makes a WireGuard key pair, and the private half never leaves the device. The public halves are exchanged, and the tunnels are built from them. The account is bookkeeping: it introduces devices, tracks where each is reachable, and hands out private addresses the public internet does not route to. MagicDNS gives each device a name you can remember and resolves it for you, so NAT, an address that changed overnight, and the switch from home Wi-Fi to mobile data all stay out of sight. ## Why the mesh works when your phone has no address Mobile data makes it worse. Your phone has no public address either: on 4G and 5G it sits behind carrier-grade NAT, sharing one outward address with many other subscribers. A design that depends on incoming connections fails at both ends. A mesh works the other way round: both sides only have to send packets *outward*. The phone sends one, the machine sends one, each learns where the other appears to be, and the two attempt a direct path through their NATs. WireGuard supplies the encryption, and the mesh supplies the introductions. Where no direct path exists, traffic falls back to a relay and stays encrypted end to end. ## A private address only your devices can reach Your machine gains a private address. Only devices signed in to the same Tailscale account can see it or reach it. The scanning bots do not know it exists, because it never appeared on the public internet. From here on, SSH goes to that address instead of a port 22 that gets knocked on all day. ## Which address do you type? The LAN address is the one to skip. Your machine holds three addresses at once, and only two of them are worth saving. | Address | Where it works | Use it? | | --- | --- | --- | | LAN, e.g. 192.168.1.23 | That Wi-Fi only | No. On mobile data it routes nowhere useful | | The ISP's address for the router | Nothing under carrier-grade NAT | No. It changes, and it is the router's | | MagicDNS name, e.g. mac-mini.tail1234.ts.net | Any network, tunnel up at both ends | Yes. Save this one | | 100.x.y.z | Same as the name | Yes, when name resolution is the broken part | A LAN address identifies a local network, not your machine. Save the name, and the entry survives the machine changing Wi-Fi, changing IP, or moving flats. `tailscale status` prints both. When SSH fails although both devices look connected, check the address first. Tailscale says connected, SSH still can't reach it covers that, including a signed-out machine. ## What does the mesh cost you? There are three costs, and you should know them before you are on a train. **One more program on the machine.** It starts at boot, updates itself occasionally, and has its own bugs. That is one more thing to rule out before you blame SSH. **A control plane you need at the wrong moment.** Traffic goes device to device, but the introductions do not. A new laptop, a replaced phone, or stale credentials are not on your network until they sign in. Fail that sign-in while you are out and the machine is fine but unreachable. Keep another way in. **A phone that has to stay connected.** The tunnel is a VPN profile, and phones drop those: after a restart, when another VPN takes over, or when you dismiss the prompt. Check it before debugging anything else. It carries only traffic to your own addresses. ## A private network still needs a key A machine nobody can reach from the internet still needs a lock on the door, for two reasons. The mesh's security is now your account's security: any device that signs in is inside, and inside means a device you already trusted. A laptop left in a taxi is inside the network you built, where every machine is reachable and nothing else checks it. A key means the machine still has to prove something. The second reason is defence in depth. The mesh keeps strangers out; a key stops a stranger who is already inside from going further. Two independent things have to fail before anyone reaches your project. A password is one guessable thing, and a key is not. How keys do the proving covers the mechanics. ## When you don't need Tailscale If the machine and the phone are on the same Wi-Fi and always will be, skip this. The LAN address works, you install nothing, and you sign in to nothing. You lose it the moment you walk outside. A hosted server changes the arithmetic. Its address does not move, there is no router of yours to configure, and a failed experiment cannot lock you out. The risk moves to port 22 facing the internet permanently. Some people still run the mesh there, to put SSH behind a private address; others keep it public and lean on keys. What is not workable: a password on an exposed port. If you already reach the machine through a network someone else runs, a work VPN for example, take that path. Two overlapping tunnels make debugging worse without making the connection better. If you are not sure which case you are in, the setup page asks three questions and hands you a checklist. The way in is settled. What is left is the phone in your hand, and what that phone assumes about why you are there. ## Why not Termius or Blink URL: https://agentmux.saxcave.cc/guide/en/why-agentmux/ Language: en Updated: 2026-11-06 Agentmux vs Termius and Blink: five capability differences, and the cases where Termius is the better tool. A general SSH client assumes you opened it to type commands. Agentmux assumes you opened it to rejoin an agent that is already running. Take Termius or Blink if you need Mosh, SOCKS5 forwarding, or host sync across platforms. Take Agentmux if your work is coding agents on several servers rather than one-off commands on one host. Both apps speak SSH and both hand you a prompt. What separates them is how much thought went into reattaching, and every row below follows from that. The five capabilities described here are all checked against the current source. ## Two tools built on different assumptions A general SSH client is built around a host and a shell: it hands you a working prompt, then stays out of the way. What happens after that prompt is yours. Agentmux is built around an agent that is already running somewhere. Its job is to shorten the distance from your pocket to that process. Three consequences follow, and the last two are where a good general client stops being a substitute. That is not a build-quality problem: those behaviours were never its product. - **What you see first.** A host, or the sessions running on it. - **What a dropped connection costs.** A prompt you re-navigate, or the screen you left. - **What the app does while you are away.** Nothing, or a Lock Screen progress line and a sound at the end. ## What each one does, capability by capability One row per capability. Where you cannot check a competitor's behaviour from outside its app, the cell says so. | Capability | General SSH client | Agentmux | | --- | --- | --- | | Back to a tmux session | tmux ls, then tmux attach -t | Sessions listed on connect; one tap reattaches | | After a drop | The prompt returns; finding the session is on you | A detach, not a death: you land on the screen you left | | A twenty-minute agent run | No notion of a run to report from | Dynamic Island and Lock Screen progress; a sound at the end | | Prompt to allow a command | You are the reviewer, in the chair | AI Permission Review: blacklist, allowlist, AI judgement; suggests or presses the key (Pro) | | One line in a file | Vim over SSH, on a phone keyboard | Runestone editor, Ink Markdown preview, branch tools, Files app (Pro) | | Private keys | Not verifiable from outside | Keychain / Keystore, never the local database; iCloud sync carries server names only | | Automation from outside | Nothing to call: no session or agent model exposed | 6 App Intents over four entity types: Server, Project, TMUXSession, RecentSession | | Unreliable mobile networks | Mosh is a reason to buy this kind of app | Standard SSH plus tmux reattachment, which is not Mosh | | Port forwarding | Local, remote, dynamic SOCKS5 | Local only | | Host configuration at scale | Synced on iOS, Android, Windows, macOS, Linux; shared host lists | Multiple servers and batch actions (Pro); mobile only | Read the last three rows first: they are where Agentmux loses. ## Why does Agentmux list tmux sessions first? Connect to a server and Agentmux lists the tmux sessions already running on it. One tap reattaches, so you do not type `tmux ls` or remember `tmux attach -t`. Reconnect after a drop and you land on the same session, on the screen you left, without retyping anything. An agent's value is the context it accumulated: which files it read, why it chose this approach. Reattach to that screen and the context survives. Open a fresh prompt and you explain the task again to a process that has forgotten it. What tmux does is the load-bearing part of this design. ## What does the app do while you are away? Agents routinely run for tens of minutes, and that waiting period is what this is built for. The Dynamic Island and the Lock Screen show progress, and a sound can play when the run ends. The app does not have to sit in the foreground while you stare at it. An app built around a shell has nothing to report once it is closed, because it holds no notion of a run. Here a named session on a named server is running, and the system reports on it. When that session stops to ask whether to allow a command, AI Permission Review can look first: dangerous commands are held, safe ones get a suggestion or a key press (Pro). ## Can you edit a file without opening vim? Changing one line of code does not mean wrestling vim on a phone keyboard. The built-in Runestone editor opens the file and lets you edit it. For a README an agent produced, Ink gives a two-column Markdown preview, source and rendered side by side. Git tools (switching branches, reading diffs) and Files app integration are Pro features, available during the 14-day trial. The loop closes on one device: the agent changes files, you read the diff, you answer, it goes on. Every step that needs a second machine waits for a desk. ## Where do your private keys live? Private keys go into native secure storage, the iOS Keychain or the Android Keystore, and never into the local database. On iOS, if you turn on iCloud sync, CloudKit carries non-sensitive metadata such as server names. The keys stay where they are. Ask any tool the same question: which of my private keys leave this device when sync is on? A feature list will not answer it. ## Siri can hand the screen to Shortcuts `GetCurrentSessionContextIntent` lets you tell Siri "get the current terminal contents from Agentmux". Whatever is on the terminal lands in iOS Shortcuts, ready for Notes, Messages, or anything else you build. `ContinueLastSessionIntent` is blunter: "continue working in Agentmux" reattaches to your last session. General SSH apps cannot do this, structurally: they have no semantic model of sessions, projects, and agents to expose to the system. Agentmux implements 6 App Intents across four entity types: Server, Project, TMUXSession, RecentSession. ## Fixes you'll feel if you read CJK Version 1.3.10 fixed three things an English-only user never notices and a Chinese, Japanese, or Korean reader spots at once: selection drifting out of alignment on full-width characters, a link that wraps across lines copying out as several separate pieces, and a lone slash inside CJK text read as a file path. ## Which tool for which job? **Mostly manual terminal work on one host.** You log in to run commands you chose: restart a service, tail a log, check a disk. Take a general SSH client. If you want to test the idea first, the free single-server tier covers one server, persistent sessions, and the core terminal. **Living inside agents on several servers.** One refactoring on the Mac mini, another running tests on a Linux box, a third started from a train. That is Agentmux, and specifically Pro: multiple servers, custom agents, remote editing, file history, branch tools, batch actions. **An agent in a browser tab or a cloud sandbox.** The convenience is real, and the cost is that the files and the credentials no longer sit on your machine. Agentmux never hosts the work: it is a client, and the code and the agent stay on hardware you own, a Mac, a Linux box, or a server. Renting one is defensible if you are at peace with your project and its keys living there. Tailscale is the recommended way in: no port open to the public internet, and machines addressed by MagicDNS names or 100.x addresses rather than LAN IPs. ## When Termius is the better buy If one of these is your main job, buy the general tool. - You work on very unreliable mobile networks and need **Mosh** to hold the connection. Agentmux speaks standard SSH and compensates with automatic tmux reattachment, which is not the same thing as Mosh. - You need **remote or dynamic (SOCKS5) port forwarding**. Agentmux only does local, and that is a capability gap rather than a preference. - You want to sync a large host configuration across iOS, Android, Windows, macOS, and Linux, or share a host list with a team. Agentmux is a mobile client: multiple servers and batch actions on Pro, nothing shared. - You are an SRE or sysadmin running operations across dozens or hundreds of servers. An inventory you script against beats a list you tap through. Agentmux is built for one workflow: **rejoining a running coding agent from your phone.** It is not a general-purpose operations tool. If your week is a stream of one-off commands, there is nothing here worth switching for. Decided on Agentmux? The next problem is the machine at the other end: the setup wizard asks three questions and hands back a checklist, and most of what goes wrong afterwards is already in troubleshooting. ## 15 minutes to an agent that works all night URL: https://agentmux.saxcave.cc/guide/en/setup/ Language: en Updated: 2026-11-06 Answer three questions and get the steps for your machine, the agent you want to run, and where you connect from: SSH, tmux, Tailscale. Setting up an agent that works all night takes about fifteen minutes: SSH into a machine you own, tmux so a dropped phone connection detaches instead of stopping the agent, and Tailscale so no port faces the public internet. Answer three questions about your machine, your agent, and where you connect from, and the wizard builds the checklist. Every step says why you're doing it, what to run, how to check that it worked, and where to look if it didn't. (Is that machine running Windows? Read this first.) The setup wizard needs JavaScript. You can also go straight to the troubleshooting page. ## Native Windows has no tmux, so use WSL2 You picked Windows, so the wizard did not hand you a checklist. That path works differently from the other three, and half a checklist would do you more harm than none. Windows can work, but the route goes through WSL2 and takes a few more steps than a Mac or a Linux box. If all you want is an agent working through the night, rent the cheapest cloud VPS you can find and follow the VPS path above. That is usually faster than getting Windows there, and it saves most people an evening. ### Why won't native Windows do? This guide rests on three pieces: the CLI, tmux, SSH. SSH is fine. Windows 10 and 11 both ship an OpenSSH Server you can switch on, and you will get a PowerShell prompt. tmux is the problem. **There is no native Windows build**, and tmux is the load-bearing piece of this whole guide. Without it your phone connection drops, the agent gets SIGHUP, and it dies (the mechanism). "Put your phone away and it keeps running" stops being true. You would have a Windows machine you can SSH into, which is not what this guide is offering. Whether the agents themselves run on Windows varies by tool and changes quickly, so there is deliberately no list here; check the documentation for the one you plan to use. Mind the order of the argument: **an agent that runs perfectly on native Windows still has no tmux.** Running and surviving a disconnect are separate questions. ### The recommended route: WSL2 Inside WSL2 you have a complete Linux: tmux, sshd, and the agents behave the way they do on real Linux. Install a distribution and every step of the "Linux box" path applies. Go back up and answer the first question that way. ### Three snags that don't exist on Mac or Linux Knowing them in advance keeps you from assuming you broke something: - **Networking.** WSL2 runs in a virtual machine with its own IP, behind NAT, so by default nothing outside that Windows host can reach the sshd inside it. There are two ways around it. Forward the port on the Windows side with `netsh interface portproxy`, and expect to redo it, because WSL's IP can change on every restart. Or turn on mirrored networking, `networkingMode=mirrored` in `.wslconfig`, so WSL shares the host's interfaces and the port-forwarding step goes away. Mirrored networking is much cleaner and needs a reasonably recent Windows 11. - **WSL doesn't stay running on its own.** When the last process exits, the distribution shuts down within seconds and takes the virtual machine with it. You want the machine to stay *always on*, so WSL has to start at boot and keep running with nobody logged in, usually through a scheduled task or a keepalive. There is nothing to do about that on a Mac or a Linux box. - **systemd is opt-in.** Older WSL has no systemd at all, so `systemctl` does not exist, and the systemd commands in the wizard's "enable the SSH service on the Linux box" step fail outright. Newer versions turn it on with `systemd=true` in `/etc/wsl.conf`, but the default is off. ### What if your Windows PC is the client? The Windows discussed here is always *the machine you connect to*. If your Windows PC is the computer you work at, and the agent runs somewhere else (a Mac mini at home, a Linux box at the office, a VPS), none of the above applies. The guide works for you as written, and the machine to set up is not the one in front of you. ## Run Claude Code on your iPhone or iPad URL: https://agentmux.saxcave.cc/guide/en/claude-code-on-iphone/ Language: en Updated: 2026-11-06 Claude Code runs on a machine you own, not on the phone. Remote Login, Tailscale, a named tmux session, and the PATH problem SSH causes. Yes. Claude Code is a program that runs on a machine you own, never on the phone. Turn on Remote Login, reach that machine through Tailscale instead of an open port, start a tmux session with a name, and run `claude` inside it. Your iPhone carries a terminal. The agent keeps working after you lock the screen. "Claude Code on my phone" names two machines doing two different jobs. Separate them and what is left gets short: what survives a dropped connection, why the command is missing over SSH, and whether an agent should answer its own permission prompts from a train. ## Does Claude Code run on the phone? Claude Code is a program on the disk of some machine you own. A shell starts it. It reads your project through that machine's filesystem, runs `git` and the tests, and writes its edits back into the same directory. None of that happens in your pocket, though the phrase suggests it does. From the phone, two things cross the wire: keys go out, characters come back. The agent is not installed there, and no copy of your repository travels. The phone draws a picture of a session running on another computer. - **On the phone.** Keystrokes, the display, your thumb. Lock the screen or swipe the app away and the display stops, and that is the whole list. - **On the machine.** The process, the files being edited, the tests, and everything the agent has already read and decided. None of it notices your screen going dark. Two consequences follow. The machine has to stay on and reachable: a laptop closed in a drawer runs nothing. The phone is the cheap half to replace. ## Three things to set up, in this order Skip one and the arrangement fails, so work through them in this order. - **A door into the machine.** On a Mac: System Settings → General → Sharing → **Remote Login**. On Linux, the SSH service, a daemon called `sshd`. - **A private network instead of an open port.** Install Tailscale on the machine and the phone, sign both into one account, then connect to the MagicDNS name or the `100.x` address. Do not use a LAN address such as `192.168.1.23`, which means nothing on mobile data. Forwarding port 22 instead hands your machine to every scanning bot that finds it. - **A tmux session with a name, and the agent inside it.** ``` tmux new -s work # name it claude # inside it ``` From the phone, on another network: ``` tmux ls # what exists tmux attach -t work # open a window onto it ``` The name matters: `tmux attach` on its own takes the most recent session, and that is a guess about which one you meant. If tmux is missing, Agentmux installs it. ## Why a plain SSH window kills the agent Skip tmux: you SSH in, run `claude`, then put the phone in your pocket and lock the screen. The phone stops sending packets, the connection times out, and it closes. sshd closes the pseudo-terminal that connection was attached to, and the kernel sends **SIGHUP** to every process still attached to it: your shell, and Claude Code running inside your shell. Both take the default reaction and exit, so the agent dies mid-edit with twenty minutes of reading gone. Keeping the app open fixes nothing. Wi-Fi switches networks, cellular loses signal, one Ctrl-C lands somewhere it should not. tmux moves the session onto the machine itself, so a dropped connection takes the window and leaves the process alone (the mechanism). **The agent goes inside the session before the work starts, because a process that is already running cannot be moved into tmux afterwards.** ## What is working from a phone actually like? A phone fits one decision at a time, which is what an agent cycle is made of anyway. It proposes a plan, and you take it or send one sentence back. A two-line correction such as "leave the serializer alone, the export job uses it" is enough, and then the phone goes away. The on-screen keyboard is the first real cost. Backticks, pipes, quotes, backslashes and the tilde all sit two layers deep, so `~/src/auth/session.ts` takes longer than it should, and one wrong segment costs a round trip. Long prompts are harder to proofread than they look, which is why many people draft them in a notes app. A diff is where a phone runs out of room. Forty changed lines across two files is three screens of scrolling on six inches, and a wrapped line hides whether a change sits at the end of a line or in the middle. Turn an iPad sideways and that same diff fits without wrapping. One habit pays for itself: a second session for the noisy parts, so a test run cannot bury the agent's prompts. ``` tmux new -s tests -d # -d starts it detached tmux attach -t tests # watch it, then detach ``` ## Can the agent answer its own permission prompts? Claude Code does not run everything it thinks of. Before it runs a command, and depending on your configuration before it edits a file, it stops and asks: here is the command, go ahead? That stop is where the job waits, and if only you can answer it, the waiting becomes the bottleneck. AI Permission Review answers those prompts and nothing else: it reads the prompt, then suggests or presses the key. A **blacklist** blocks dangerous commands and always hands a match to you. An **allowlist** passes commands you already trust, decided on the device. Anything matching neither goes to an **AI judgement**: allow, deny, or unsure, and the last two come back to you. The session's mode then decides whether it only **suggests** the key or **presses** it. Distance does not move the responsibility off you. A command you approve on cellular runs on a machine that holds your `.env`, your keys, and a repository you do not own alone, and the judgement sees the screen, not the reasoning that got the agent there. Start in the mode that only suggests, allowlist the commands you approve every day, and read the screen before pressing anything when the signal and your attention are both poor. The blacklist exists because "approve everything" is a bad default: an `rm -rf` aimed at a home directory is no gentler because you said yes on a train platform. ## claude: command not found, but it runs in Terminal Same machine, same account, two different answers: `claude` starts in Terminal and prints `claude: command not found` over SSH. That reads like the remote setup is broken. It is the most common failure on the way in. An install through npm, or under a node version nvm pins, writes its directory into a startup file that only an *interactive login* shell reads, and that is how the PATH gets the entry. A connection that runs one command, like `ssh host claude`, starts a non-interactive shell, and that shell never reads the file. The fix that always works is to stop relying on the PATH. In the Terminal where it runs: ``` which claude # /Users/you/.nvm/versions/node/v20.11.0/bin/claude ``` Use that absolute path, either as the agent command or typed into the session. It breaks when nvm changes the node version. The durable fix depends on your shell and your distribution, and the troubleshooting page covers it. ## Can the agent work overnight? A session on a machine that stays awake runs all night: hand it a refactor at eleven and read the result over coffee. Three things get in the way. **The machine has to stay awake.** A Mac that idles into sleep takes its network interface with it, and Remote Login makes no difference because nothing is left to answer. System Settings → Battery on a laptop, Energy Saver on a desktop: lengthen or switch off idle sleep, and turn on "Wake for network access" if your Mac offers it. **A reboot takes the session with it.** tmux does not restore sessions after a restart, and a software update reboots the machine without asking. Commit before you stop for the night, so the worst case costs an hour instead of the whole task. **A session can be waiting rather than dead.** Attach and look. A fresh shell prompt means the process exited: finished, or died, and the lines above say which. A prompt asking you a question has waited six hours for one word, at no cost. From a lock screen both look the same, and only one needs you to act. ## What belongs on the phone, what belongs at the desk | From the phone | At the desk | | --- | --- | | Answer a permission prompt, having read it | Review a diff you did not expect to see | | Choose between the two approaches it offered | Judge a change spanning several files | | Read one short diff, commit, push | Read a long diff, or a file you have never opened | | Watch a test run in a second session | Debug the failure those tests printed | | Hand it the next task before bed | Write a prompt that has to be exact | | Reattach after ten dropped connections | Fix the connection, or the PATH problem behind it | Three things have to hold: a machine that stays awake, a session with a name, and a private way in. If one is missing, the setup wizard asks three questions about your machine and hands back a checklist. ## AI Permission Review: who answers when your agent asks URL: https://agentmux.saxcave.cc/guide/en/permission-review/ Language: en Updated: 2026-11-06 Who answers when an agent asks to run a command: a local blacklist blocks it, an allowlist clears it, or AI judgement decides. AI Permission Review answers the permission prompt an agent raises when you are not looking at the screen. A blacklist blocks dangerous commands first, an allowlist passes the commands you already trust, and an AI judgement decides the rest. The session's mode then decides whether it only suggests the key or presses it for you. Halfway through a task the agent stops and asks "Run this command?" You happen to be away from the screen, and the whole job waits on one key press. AI Permission Review exists for that moment. It answers the prompt, one key at a time. It does not type the command, and it does not touch anything else on the screen. - **The agent stops and waits**Agentmux detects a permission prompt - **Read the screen**Find the current prompt and redact sensitive text - **Blacklist**A dangerous command matched*Handed to you* - **Allowlist**A command you trust matched; pick the key locally - **AI judgement**Allow, deny, or unsure*Deny or unsure: handed to you* - **Re-check before sending**If the screen changed, nothing is sent*Handed to you* - **Suggest, or press for you**Depends on the mode you picked ## It starts only when an agent stops and waits Agentmux already tracks the state of agents like Claude Code and Codex. When a prompt such as "Allow this?" or "Pick 1 or 2" appears on screen, the session is marked as waiting, and the review starts from there. These also need to be true: - The AI Permission Review switch in Settings is on. - The session's mode is Advisor or Auto Reply, not Off. - You have Pro, or you're still in the free trial. Each prompt is judged once, and Agentmux does not re-run that check every few seconds. If you press a key or type in the terminal yourself, it cancels the review in progress and takes your input instead. Switch to another tab and the review keeps running in the background, on the sessions you are no longer looking at. ## It reads the current prompt, not the scrollback It picks out the prompt currently on screen: the command to run, the question itself, and the choices offered. Anything that has already scrolled up does not count. Before the screen goes to the AI, Agentmux redacts anything that looks like a password, API key, or token. If that redaction fails, the text is not sent and no key is pressed this time. ## The blacklist runs first, on your phone Your phone does the matching, and the blacklist runs before anything else. A match is never sent to the AI: the prompt comes straight to you. The built-in rules cover the most dangerous kinds of operations: - `rm -rf` on `/`, `~`, or your home directory - `sudo` - `git push --force` (`--force-with-lease` doesn't count) - `DROP TABLE`, `DROP DATABASE`, `TRUNCATE TABLE` - Piping something downloaded straight into a shell, like `curl … | sh` - Commands that write directly to a disk, like `mkfs` and `dd if=` You can edit or remove these built-in rules, and add your own. ## The allowlist decides without the AI Some commands you approve every day, like a test run or a build. Add them to the allowlist and the next match is settled on your phone. It picks the key locally, so there is no wait for the AI and no API usage. In Auto Reply mode you can also turn on "Allow all commands in this session" for a single session. That skips the AI judgement. The blacklist still applies, and the setting turns itself off when the session ends. If the allowlist matches but the screen shows no clear, safe key, the prompt comes to you instead. ## What does the AI decide? Only prompts that match neither list reach the AI. It gets the redacted screen plus your **Main Intent**, which is what you want the agent to do this time, for example "fix the login page tests". You can choose between two engines: - **JEV decision engine** (through OpenRouter or TypeSafe): built for terminal prompts, and fast. It decides in three tiers. Safe read-only operations are allowed. Commands that match your Main Intent are allowed. Everything else comes to you, whether it is unrelated, unclear, or high-risk. Anything that looks suspicious or like a malicious injection is denied outright. - **An OpenAI-compatible LLM**: OpenAI, OpenRouter, or a model you run on your own machine. The AI reaches one of three conclusions: allow, deny, or unsure. Only "allow" goes further, and the AI has to name the exact option on screen it wants to pick. It never picks a permanent grant such as "Always allow" or "Don't ask again". ## Advisor suggests, Auto Reply presses the key Each session has its own mode: - **Advisor** (default): suggestions only. A notice at the top of the screen tells you what it recommends, with an Accept button next to it. Nothing is sent until you tap it. - **Auto Reply**: when the verdict is allow, it presses the key for you and shows a notice saying which key it pressed. Either way, it looks at the screen again right before sending a key. If the prompt changed, the options moved, or the cursor is not on the target, it sends nothing. The judgement takes a few seconds, and the prompt can change in that time, so the second look is what stops a wrong option from being pressed. After Auto Reply sends a key, Agentmux waits 3 seconds before sending another one in that session. ## When does it hand the prompt back to you? - A blacklist rule matched. - The AI said deny, or was unsure. - It cannot tell which key the prompt wants. - The choice is a permanent grant such as "Always allow". - The screen changed before the key was sent. - Redacting the sensitive text failed. Then Agentmux alerts you the same way it would with the review turned off. ## How do I turn it on? Before you start, have one of these ready: - An API key for the JEV engine, from OpenRouter or TypeSafe. - Your own OpenAI-compatible LLM endpoint, such as the OpenAI API or a model running on your own computer. - Open Settings → AI Permission Review and turn on the switch. The first time, you see an explanation of what data is shared. Tap to agree and it takes you to Advanced Settings. - In Advanced Settings, choose an engine, enter your API key, and tap "Test AI Endpoint Connection" to confirm that it works. - Back in Settings, pick a default under Permission Review Mode (Advisor is a reasonable start), then write your Main Intent. - Connect to a server and start an agent. New tabs start in the default mode. To change the mode or the Main Intent for one session, tap the Permission Review button in the terminal toolbar. There you can also turn on "Use My Latest Prompt", so the Main Intent follows each prompt you send in that tab. Run Advisor for a few days first and compare its suggestions with how you actually answer. Then turn on Auto Reply for the sessions where the pattern holds. ## What data leaves your device? Only two things leave it: the redacted text on screen and your Main Intent. They go to the AI service you set up, using your own API key. With "Use My Latest Prompt" on, the prompts you send in that tab become the Main Intent, so they are sent as well. Agentmux does not provide or relay an AI service. The record of each decision stays on your device, where you can review or clear it in the audit log. Model reasoning is not stored. ## Stuck? Start here URL: https://agentmux.saxcave.cc/guide/en/troubleshooting/ Language: en Updated: 2026-11-06 SSH refused, key rejected, tmux missing, claude not found: eight failures, each with a cause and a command to type. Remote development over SSH, tmux and Tailscale fails in eight recognizable ways: `Operation timed out`, `Permission denied (publickey)`, a password that is correct but rejected, `tmux not found`, `claude: command not found`, Tailscale showing connected but unreachable, Remote Login on but the Mac refusing, and `sudo: a terminal is required`. Each one has a symptom, a cause, and a fix. Follow the previous articles and it works first time eight or nine times out of ten. When it does not, it is almost always one of these eight. Find your error message and jump to it. Nobody needs to read this page from the top. ## Connection timed out (Operation timed out) ### Symptom You run `ssh user@host` and the screen just sits there. After a long wait comes `Operation timed out` or `Connection timed out`. SSH never gets as far as asking you for a password. ### Causes - A typo in the hostname, say `my-serverr`. SSH reports `Could not resolve hostname` straight away instead of hanging, so if you see that message, go check the spelling. A hang means a wrong IP: a missing octet, one wrong digit, or an address where nothing is listening. - SSH isn't on the standard port 22 on the other end and you left out `-p`. - The target machine is asleep, or simply switched off. - A firewall in between is dropping the connection: your home router, a corporate network, hotel Wi-Fi. The machine never receives your request. ### Fix A timeout means your packets never reached the machine, so confirm one hop at a time instead of guessing. First check whether the machine responds at all: ``` ping host-address ``` Nothing back from `ping`? Check the IP for typos, then check whether the machine is asleep or powered off. Some machines block `ping` on purpose, so a failure here does not prove the machine is unreachable. Go straight to the next step. Next, check whether anything is actually listening on the SSH port: ``` nc -vzw 5 host-address 22 ``` Replace `22` with the port the other end really uses. The `-w 5` caps the wait at five seconds, so a blocked port gives you an answer instead of hanging. You get one of three outcomes. `succeeded` or `open`: the port is reachable, so the problem is elsewhere. `Connection refused`: the machine received you, but nothing is listening there. Another timeout: something in between is dropping the packets, usually a firewall. Look at the router configuration or the network you are on, not at the server. ## Permission denied (publickey) ### Symptom The connection is refused the instant you make it. It prints `Permission denied (publickey).` and never asks you for a password. ### Cause The server will not accept the key you are presenting. Most often the public key was never put on the server at all. The other cause is easy to miss: permissions on `~/.ssh` or `authorized_keys` that are too loose. SSH is strict here. If the permissions are wrong it ignores the file completely, no matter how correct the key inside is. ### Fix When a key is refused, the usual reason is that the server never received it. If you have not copied the public key across, the quickest route is `ssh-copy-id`: it logs in once with a password and appends your local public key to the server's `authorized_keys`. ``` ssh-copy-id user@host ``` That assumes password login is still enabled on the server. If it has already been turned off (see Password is correct but rejected below), you'll need someone with console access to that machine to paste the public key into `~/.ssh/authorized_keys` directly. If the key really is in there, one per line, complete and not truncated, and you are still refused, permissions are the likely culprit. Check them against the table. The last row is the one people miss: get `~/.ssh` and `authorized_keys` both right, leave your home directory group- or world-writable, and `StrictModes` still refuses. | Path | Required permissions | Command | | --- | --- | --- | | ~/.ssh | 700 | chmod 700 ~/.ssh | | ~/.ssh/authorized_keys | 600 | chmod 600 ~/.ssh/authorized_keys | | ~ (the home directory itself) | Not writable by group or others | chmod go-w ~ | Try connecting again once the permissions are fixed. If it still fails, add `-v` locally to watch the exchange and confirm SSH is really offering the key you think it is: ``` ssh -v user@host ``` That runs to fifty or sixty lines. You only need two of them. `Offering public key: ...` means your key really was sent, so nothing is wrong at your end. If `Authentications that can continue: publickey` follows it, the server received the key and declined it. The key is in the wrong place or the permissions are wrong, so carry on with the steps above. ## Password is correct but rejected ### Symptom You're certain the password is right, and you may well have pasted it, but SSH keeps returning `Permission denied, please try again.` Sometimes it ends without prompting you at all. ### Cause This usually has nothing to do with the password. The server's SSH configuration has password authentication switched off (`PasswordAuthentication no`). That is a common hardening choice: it forces everyone onto keys and removes password guessing as an attack. With it set, a correct password gets refused a hundred times out of a hundred. The next part costs people more time than anything else on this page. Newer systems (Ubuntu after 22.10, Debian 12, nearly every cloud provider's default image) put a line near the top of `sshd_config` reading `Include /etc/ssh/sshd_config.d/*.conf`, which pulls in every config file in that directory. For any one setting, sshd keeps the *first* value it reads, so a later one does not override it. If `/etc/ssh/sshd_config.d/` holds something like `50-cloud-init.conf` that also says `PasswordAuthentication no`, that file wins over the line you just edited in the main file. You save the file, restart the service, and you are still refused. ### Fix The better fix is key authentication: see Permission denied (publickey) above for getting your public key into the server's `authorized_keys`. Only change the configuration if you administer this server and have decided you do want password login. First find out whether another file will override the value you're about to edit: ``` sudo grep -r PasswordAuthentication /etc/ssh/sshd_config /etc/ssh/sshd_config.d/ ``` If a file under `/etc/ssh/sshd_config.d/` also sets `PasswordAuthentication`, change that value too (or comment the line out). Once that's clean, edit `/etc/ssh/sshd_config` and set `PasswordAuthentication` to `yes`. **Don't close your current connection yet.** A broken sshd configuration can lock you out of the machine for good. Keep this window as your way back in until the new one is confirmed working. Check the syntax before anything else: ``` sudo sshd -t ``` When it prints no errors, restart the service to apply the change: ``` sudo systemctl restart ssh ``` On some distributions the service is called `sshd` rather than `ssh`. If the line above cannot find it, try `sudo systemctl restart sshd`. Then open a brand-new connection, not the one you kept as a fallback, and confirm password login works. Close the old window only after that. This whole setup runs on Tailscale, so there is a safer option than opening password login to the entire internet: allow passwords from the Tailscale range only, and require keys everywhere else. Add this at the end of `sshd_config`: ``` Match Address 100.64.0.0/10 PasswordAuthentication yes Match All PasswordAuthentication no ``` ## tmux not found ### Symptom Agentmux reports that it can't find tmux, or you connect yourself, type `tmux`, and get `command not found: tmux`. ### Cause tmux is not installed on the target machine yet, and Agentmux is the one meant to install it: it detects whether the machine uses apt, yum, dnf, pacman, apk (Alpine), or brew, then runs the matching command over SSH. Usually you wait for it to finish. When that fails, the usual reason is `sudo`. The install command includes it, the channel is non-interactive, and `sudo` has no terminal to ask for a password on (see sudo: a terminal is required to read the password below). Second most common: Homebrew on a Mac while connected as root. Homebrew refuses to run as root by design. A rarer case is easy to misread. tmux *is* installed, just somewhere Agentmux did not see when it probed the PATH, such as Linuxbrew under `~/.linuxbrew` or MacPorts under `/opt/local/bin`. The message reads the same, but the problem is the PATH, not a missing install. Handle it as described in claude: command not found, but it runs fine in the desktop terminal below. ### Fix Start with the error Agentmux showed you. If it asks for a sudo password, or mentions passwordless sudo, type the password in and retry. That is usually faster than opening a separate window. If that fails too, or the message mentions a terminal or TTY, work through sudo: a terminal is required to read the password yourself. Once that is sorted the automatic install normally succeeds and you can stop there. Install by hand only if it still won't install after the `sudo` problem is ruled out: ``` # Debian / Ubuntu sudo apt install -y tmux # RHEL / CentOS sudo yum install -y tmux # Fedora sudo dnf install -y tmux # Arch sudo pacman -S tmux # Alpine sudo apk add tmux # Mac (do not run as root) brew install tmux ``` ## claude: command not found, but it runs fine in the desktop terminal ### Symptom You type `claude` in Terminal on the Mac and it runs. Connect through Agentmux, or run `ssh host claude` directly, and you get `claude: command not found`, or `bash: claude: command not found`. It is the same machine and the same account, so it looks exactly like Agentmux being broken. ### Cause It is not Agentmux. It is how shells behave. This is the hardest one on the page to work out on your own, so the mechanism is worth the space: once you have it, you can diagnose anything shaped like this. The Terminal you open on the Mac is an *interactive login shell*. `claude` is on the PATH there because nvm, npm, or an installer added a line to a startup file that puts its directory on the PATH. *Which* startup file that is differs between zsh and bash, and mixing them up sends people hunting in the wrong place. Take the two shells one at a time: **zsh** (the default on macOS): an interactive login shell reads `.zshenv` → `.zprofile` → `.zshrc` → `.zlogin`, in that order. A connection that runs a single command, like `ssh host claude`, starts a non-interactive, non-login zsh that reads only `.zshenv` and skips everything else, including `.zshrc`, which is where most people's PATH setup lives. **bash**: here the behaviour varies by distribution, so there is no single answer. An interactive login shell reads `.bash_profile` (or `.profile`), not `.bashrc`. You probably think bash reads `.bashrc` as well because most distributions ship a `.bash_profile` containing a snippet that sources `.bashrc` if the file exists. The `ssh host claude` case is the odd one: sshd invokes bash to run a single command, and whether bash then reads `~/.bashrc` comes down to a compile-time option called `SSH_SOURCE_BASHRC`. Debian, Ubuntu, and their derivatives carry that patch, so even a non-interactive single-command shell reads `~/.bashrc`. RHEL, Fedora, and Arch ship upstream bash without it, and there `~/.bashrc` is never read at all. Even on distributions that do read it, almost every default `.bashrc` opens with: ``` case $- in *i*) ;; *) return;; esac ``` which means "if this isn't interactive, stop here and read no further." That check is usually right at the top of the file, so even when bash does open `.bashrc`, it bails out before reaching the PATH line you added, the same outcome as never reading it. With zsh or bash the result is the same. The line that sets the PATH only ever ran when you opened Terminal yourself. SSH running a single command never read it, so the PATH has no entry for `claude`, and the shell is quite correctly reporting that it cannot find the command. Whether `claude` is installed has nothing to do with it. The command was there the whole time, and nobody told this particular shell where to look. Skipping those files for non-interactive runs is deliberate. Otherwise every remote command would first load a pile of interactive aliases and prompt settings, and pick up that many more ways to go wrong. One more thing worth knowing. Agentmux probes the PATH on a Mac with `zsh -lc` (login, not interactive), which also skips `.zshrc` and reads only `.zshenv` and `.zprofile`. So for zsh users on a Mac, moving the PATH setup into those two files does more than fix your own manual SSH. It decides whether Agentmux can find the agent command at all. ### Fix Quickest option: find the absolute path to `claude` and call it directly instead of relying on the PATH. In the Terminal window where `claude` already works, run: ``` which claude ``` That prints something like `/Users/yourname/.nvm/versions/node/v20.11.0/bin/claude`. Put the whole path into the field where Agentmux asks for the agent command, in place of plain `claude`. If you installed node with nvm, that path has the version number in it, so it breaks the next time you upgrade and you run `which claude` again. To stop redoing that, use the durable fix below. The durable fix depends on your shell, so do not mix the two: **On zsh**, the macOS default: move the lines that set the PATH out of `.zshrc` and into `~/.zshenv`. Usually those come from nvm, pyenv, or an `export PATH=...` you added yourself. `~/.zprofile` works too: both are read by non-interactive SSH runs and by Agentmux when it probes. **On bash**, moving them to `~/.profile` or `~/.bash_profile` will not help. `ssh host claude` starts a non-login shell and neither file is read. On Debian, Ubuntu, or a derivative, put the PATH lines in `~/.bashrc` *above* the `case $- in ... esac` block at the top, so they run before the interactive check bails out. On RHEL, Fedora, Arch and similar, `~/.bashrc` is not read here at all and editing it changes nothing. If you don't know which group your distribution is in, use the absolute path. It works everywhere, and it is the only fix guaranteed to hold across distributions. Once you've moved them, open a fresh SSH connection and confirm it took effect: ``` ssh host 'claude --version' ``` A version number means the PATH really is being read in a non-interactive shell this time. ## Tailscale says connected, SSH still can't reach it ### Symptom The Tailscale app on your phone shows both devices green and online, but connecting with Agentmux or plain `ssh` still fails or times out entirely. ### Causes - You are connecting to a local network IP such as `192.168.x.x`, rather than the MagicDNS name or the `100.x` Tailscale IP. A local IP only works on that same Wi-Fi. The moment the phone switches to mobile data, the address means nothing. - The other device has just dropped off or gone to sleep, and the "online" state in the app hasn't caught up. - Tailscale is working fine and the SSH service on the target machine was never enabled or started. Tailscale connects the two machines' networks. It does not turn SSH on for you. - The most common cause is an expired node key. The target machine needs to reauthenticate, and the app still shows a green "connected" while the machine is no longer on the network. Check the Tailscale admin console for a machine flagged as needing to sign in again. Reauthenticating usually settles it. ### Fix Start on the target machine, because the app's green light can be stale. Tailscale on a phone has no command line, so `tailscale status` only runs on that machine, the Mac or Linux box you are connecting to: ``` tailscale status ``` On the target machine, confirm the phone's node shows as online. On the phone, open the Tailscale app and confirm the target machine looks healthy too: not greyed out, and no "needs to sign in again" warning. If both look right, connect using the MagicDNS name (`mac-mini.tail1234.ts.net`, for example) or the `100.x` IP from `tailscale status`, never the local network IP. That way the address holds whether you are on home Wi-Fi or on mobile data. Once all of that checks out, go back to the target machine and confirm the SSH service is running: on Linux `sudo systemctl status ssh`, on a Mac see Remote Login is on but the Mac still won't accept connections below. ## Remote Login is on but the Mac still won't accept connections ### Symptom Remote Login is enabled in System Settings and the Mac is definitely on the network, but SSH still can't connect. Sometimes it connects and you are refused anyway, with a password or key you are sure is correct. ### Causes - Remote Login defaults to allowing only the accounts in its "Only these users" list, and the account you are connecting as may not be on it. - Once the Mac sleeps the network interface sleeps with it, so SSH has nothing to reach. Remote Login being on makes no difference. The machine is not answering network requests at all. ### Fix Open System Settings → General → Sharing. That is the path on macOS Ventura and later; before Ventura it is System Preferences → Sharing, with no "General" level. Find Remote Login, confirm the switch is on, then look inside at the list of users allowed access and check your account is there, or select "All users". Next open System Settings → Battery on a laptop, or System Settings → Energy Saver on a desktop that stays on as a server (a Mac mini, Mac Studio, or iMac, which is what `mac-mini.tail1234.ts.net` above would be). Increase or disable the idle sleep timer. If there is a wake-for-network option, usually labelled "Wake for network access", turn it on too. The Mac can then wake on an incoming connection and answer SSH from sleep. ## sudo: a terminal is required to read the password ### Symptom Running a command containing `sudo` through Agentmux or SSH fails immediately with `sudo: a terminal is required to read the password`, showing no sign of having run at all. ### Cause Normally `sudo` prints a password prompt to the terminal and waits for you to type. When the command arrives over a non-interactive channel (SSH running a single remote command, or an install Agentmux runs in the background), there is no terminal to show a prompt on and nowhere for you to type. With no TTY to write to, `sudo` refuses outright instead of waiting for input that will never arrive. Plenty of people assume that once they have typed a sudo password, sudo will not ask again for a while. Modern sudo does record that you authenticated, but that record is tied to the TTY you were on (`tty_tickets`): not to your account, and not to the machine. You type your password in an interactive SSH window, and moments later Agentmux runs a command over a fresh connection with no TTY. As far as sudo is concerned that is a different record, so it asks you again. Nothing expired. The two runs never consult the same record. ### Fix A non-interactive channel gives sudo nowhere to ask, so either let Agentmux collect the password or run the command once yourself in a real terminal. Start with what Agentmux is telling you: when an automatic install fails it usually offers to take a sudo password, or suggests passwordless sudo, then retries. If you see that field, filling it in is often faster than handling this yourself. If that channel is not available, SSH into the machine yourself to get a normal interactive session, then run the command by hand, `sudo apt install -y tmux` for example, entering the password when it is asked for. That works, though not for the reason people assume. It unlocks nothing for Agentmux's later automated runs, because the two consult different TTY records. It works because you installed the software yourself, so the next non-interactive command finds it already there and has nothing left to install. If one specific command needs to run passwordless over non-interactive channels long-term, `sudo visudo` can add a `NOPASSWD` rule scoped to that command alone: ``` youraccount ALL=(ALL) NOPASSWD: /usr/bin/apt ``` That trade-off is worse than it looks: granting `NOPASSWD` to a package manager like apt is very nearly granting full root. apt can be told to install arbitrary packages, and hooks such as `APT::Update::Pre-Invoke` run arbitrary commands as root while the install runs. In practice that is passwordless root written a different way. Configure it only if you understand that cost and accept it deliberately. Otherwise go back to installing once by hand. And never grant a blanket `NOPASSWD: ALL` — that drops the password requirement from `sudo` across the whole machine. --- # Guide — 繁體中文 ## 你的 AI 應該在你睡覺時工作 URL: https://agentmux.saxcave.cc/guide/zh-TW/why-remote-vibe-coding/ Language: zh-TW Updated: 2026-11-06 遠端跑 agent 的成本是待命:機器不關機、tmux、SSH 加 Tailscale,待命變成一支手機加十五秒。 CLI agent 真正的成本是待命,不是打字:你講三十秒,接下來幾個小時不能離開位子。把它跑在你從來沒關過的機器上,用 tmux 讓 session 斷線照跑,再用 SSH 加 Tailscale 從手機接回去,待命就變成一部手機加十五秒,而它在你睡覺的時候繼續工作。 如果你已經在用 Claude Code 或 Codex,大概感覺過這件事:做事的是它,被綁住的是你。會被綁住不是因為你不肯放手,是因為那個 agent 只住在你這台機器上的一個視窗裡。 ## 時間花到哪裡去了? 一輪 CLI agent 的工作,節奏幾乎都是同一個樣子: ``` 你講 30 秒 → 它跑 5–20 分鐘 → 它需要你決定一件事 → 重複 ``` 把一整天的比例攤開來看,吃掉時間的不是打字。你描述需求那 30 秒,是全程最短的一截。剩下全是等:等它讀完專案,等它改完十幾個檔案,等測試跑完。然後它停下來問:「這個 migration 要不要順便處理?」你回一句話,接著又等五分鐘、二十分鐘。 一天下來,你真正打字的時間可能不到十分鐘,被佔掉的是好幾個小時。這幾個小時你在**待命**:什麼都沒做,但不能走開,因為它隨時可能要你回一句話。 ## 傳統做法把待命綁死在一台筆電上 這種待命有一個實體位置。agent 跑在你筆電的終端機視窗裡,視窗連著那個 shell,shell 連著那台不能關掉的機器。於是你只有兩個選擇: **坐著等。**人留在椅子上,盯著那個大部分時間什麼都不發生的畫面,只為了在它問的時候三十秒內回話。一小時換三句回覆。 **或者關掉筆電。**合上蓋子,process 被停掉或直接死掉,session 沒了,agent 累積下來的脈絡也一起消失:它讀過哪些檔案、改到哪一步、為什麼選這個做法。晚點回來只能重開一個,整段重講一次,從頭跑。 兩個選擇付的是同一筆代價。真正綁住你的是**那台機器在哪裡**,不是工作本身。 ## 哪三個零件解開這個綁定? 這個綁定不是一整塊,是三層疊起來的。每一層都有現成的標準工具可以拆,而那些工具都已經存在幾十年了。單獨看,三樣都很普通;疊起來才是重點: | 零件 | 解開的綁定 | | --- | --- | | CLI | agent 是純文字 process,不需要圖形介面 → 可以住在別台機器上 | | tmux | session 釘在伺服器上,斷線照跑 → 待命不需要你在場 | | SSH + Tailscale | 從任何網路安全接回,不開公網 port、不需固定 IP → 待命不綁定裝置與位置 | 順序不能換。先有 CLI,agent 才可能離開你的筆電,搬到家裡那台從來沒關過的 Mac mini 上。接著是 tmux,session 釘在那台機器上,你的連線斷掉它照樣跑。最後是 SSH 加 Tailscale,你才能從捷運上、從公司、從別人家的 Wi-Fi 接回同一個 session,而且不用在路由器上開任何一個對外的 port。 少任何一個都不成立。只有 CLI 沒有 tmux:一斷線,agent 就死。有 tmux,沒有安全的進入方式:你只能在家裡用它。 ## 待命的成本是一部手機加十五秒 三層都拆掉之後,待命的代價從**一台筆電加一個固定位置**,降到一部手機加十五秒。要確認它跑到哪裡,你不用再找桌子、位子和電源。 只要十五秒,你就不用再排一段時間來顧它。排隊看一眼,等紅燈看一眼,睡前看一眼,回一句「用第二個方案」,手機收回口袋,agent 繼續往前跑。 待命一旦不需要你在場,工作就能排進你本來就不在場的時間。丟一個重構任務給它,去吃晚餐。睡前開一個修測試的 session,早上起床看結果。那台機器每一個晚上本來都閒著。 ## 還有其他做法能接到自己的 agent 嗎? 要從遠端接到自己的 agent,路不只一條。把這些做法放在同一組標準下比,差別就很清楚,尤其是最後一欄。多數人是事後才發現自己卡在那一欄。 | 做法 | 待命成本 | 脈絡存活 | 需要的網路設定 | 能不能真的改檔案 | | --- | --- | --- | --- | --- | | 帶著筆電 | 要帶一個包、找位子、找電源 | 合上蓋子就停 | 不用,但機器得跟著你走 | 能,改的是本機的檔案 | | VNC 遠端桌面 | 需要一塊夠大的螢幕,還要用手指操作滑鼠 UI | 桌面 session 留著就活著 | 要打通 VNC 或另外架 VPN | 能 | | 網頁版 agent | 低,開瀏覽器就好 | 活在對方的伺服器上 | 不用 | 改的是雲端沙箱裡的副本,不是你機器上那份 | | Codespaces 這類雲端環境 | 低,但要能開瀏覽器 | 閒置久了容器會被回收 | 不用,但要把專案放上雲端,並依用量計費 | 能,但改的是雲端容器裡的那份 repo | | 通用 SSH App | 手機就行 | 靠 tmux,撐得住 | 要自己處理安全連外 | 能,但介面是把桌面終端機直接搬過來 | | Agentmux | 手機,十五秒接回 | tmux 在伺服器上,跟手機在不在無關 | Tailscale,不開公網 port | 能,改的就是你自己機器上那一份 | 跑在別人雲端沙箱裡的 agent,改的永遠是一份副本:你的 `.env` 不在那裡,今天早上裝的那個內部套件不在,那個只有本機跑得起來的資料庫也不在。 走 SSH 加 tmux 這條路,agent 就坐在你自己的機器上,用你的環境,改你的檔案。唯一的差別是這一次換你的手機看著它。 不確定該從哪裡開始?先幫我判斷該走哪條路。 ## 為什麼不是 Cursor / VS Code URL: https://agentmux.saxcave.cc/guide/zh-TW/why-not-gui-ide/ Language: zh-TW Updated: 2026-11-06 GUI IDE 的 agent 綁在一個需要螢幕的 process 上,CLI agent 的介面是一條可以重新接上的位元組流。這是架構差異,不是偏好差異。 Cursor 或 VS Code 裡的 agent 是 GUI 程式的一部分,所以需要螢幕、一個已登入的桌面,和一個坐在前面的人。CLI agent 的介面是一條字元流,tmux 可以把整條流存下來,再畫到任何一塊螢幕上。這不是比較喜歡哪一個的問題,是架構上的差別。 你已經有 Cursor,或是 VS Code 加一個 agent 外掛,用得很順。為了同一件事去學 SSH 和 tmux,只有一種情況划算:你人不在那台電腦前面。 ## 差別在架構,不在偏好 GUI IDE 裡的 agent 是那個 GUI 程式的一部分。它活在應用程式的 process 裡,狀態存在那個視窗的記憶體裡,輸出畫在那塊畫布上。所以它需要一塊螢幕、一個已登入的桌面 session、一個坐在前面的人。關掉視窗,agent 就跟著結束。這不是誰的失誤:agent 本來就在那個 process 裡面。 CLI agent 的介面是一條位元組流:讀進來字元,吐出去字元,中間沒有圖形的東西。這種流可以被**接上、拔掉、再接回去**。 tmux 做得到這件事,是因為一個 80×24 的字元矩陣可以完整存下來,再畫到任何一塊螢幕上:你的筆電、你的手機、你朋友的 iPad。換一台裝置不會少掉任何東西,因為那些內容本來就只是字元。 CLI agent 能在遠端跑,理由只有這一個:**一塊像素畫布沒法序列化之後再接回來,一個字元矩陣可以**。這話說的是能力,不是用文字介面的人比較厲害。 ## 合上筆電之後會發生什麼事? 假設你下班前丟了一個大重構給 agent,估計要跑二十分鐘以上,而你現在要走了。 | Cursor 開著 | tmux + Claude Code | | --- | --- | | 合上筆電 | agent 停 | agent 繼續跑 | | 兩小時後 | 打開,脈絡斷了,重跑 | 手機打開,看到它做完了 | 右邊那一欄跟 agent 有多聰明沒關係。那個 session 從頭到尾就不住在你的筆電上。它住在那台一直沒關的機器上,你的筆電只是接上去用了一陣子。合上蓋子,斷掉的是連線,不是工作。 Cursor 現在也有雲端背景 agent,闔上筆電一樣繼續跑。但繼續跑的是 Cursor 自己遠端沙箱裡的那份副本,不是你機器上這一份。它跟本站另一篇講過的網頁版 agent、Codespaces 是同一類東西,左欄寫的是你機器上那一個。 ## 「那用遠端桌面(VNC)不就好了?」 這是最常見的反駁,理論上完全講得通:桌面留在那台機器上,你用 VNC 連回去,Cursor 一樣開著,看起來就解決了。 實務上兩者傳的東西根本不同。**VNC 傳像素,SSH 傳字元。**一整塊螢幕更新是幾百 KB 到幾 MB 的影像資料,同一塊畫面用字元表示只有幾 KB 的文字,大約差了一千倍。 這是你天天會踩到的差距。在捷運上、在電梯裡、在訊號只剩一格的地方,VNC 變成卡卡的幻燈片,滑一次要等三秒才畫出來。同樣的網路,SSH 的字元照樣即時送到。 就算頻寬完全夠用,那個桌面 UI 本來是給滑鼠和 1440p 螢幕設計的,現在被塞進六吋的觸控螢幕。點一個選單要先放大、再對準、再點。連線再快,那個介面也不會變得好用:它從來不是為觸控設計的。 ## 坐在桌前,IDE 常常更好用 IDE 不是錯的工具。坐在桌前寫 code,IDE 常常更好用:跳轉定義、重構、除錯器,這些在手機上都做不到。**IDE 適合「你在場、你主導」,CLI agent 加遠端適合「它在跑、你抽查」。**用得好的人兩個都留著:白天在 Mac 上用 IDE,離開座位用 Agentmux 接回同一個專案的同一個 agent。 Agentmux 不是要取代你的 IDE,它取代的是**坐在椅子上等**。然後你把手機放進口袋,走進沒有訊號的樓梯間,連線斷了。那個 session 接下來會怎樣,要看你怎麼接回去。 ## 為什麼最強的 coding agent 都住在終端機裡 URL: https://agentmux.saxcave.cc/guide/zh-TW/what-is-cli/ Language: zh-TW Updated: 2026-11-06 因為終端機給的是完整檔案存取和執行權限,別的介面做不到。終端機是什麼,什麼時候該用 GUI。 CLI agent 是一個用文字跟你對話的 coding agent。它跑在你自己擁有的機器上,一台 Mac、一台 Linux 主機、或一台伺服器,並沿用你在那台機器上的權限:讀整個專案的檔案、執行任何指令、把結果讀回來。它住在終端機裡,因為沒有別的介面能同時給你這三樣。 「CLI」三個字聽起來像懲罰:沒有滑鼠、沒有按鈕、只有一個閃爍的游標。可是 Claude Code、Codex、Gemini CLI、OpenCode 全都住在這裡。做它們的人不是硬派,是因為終端機給了 agent 兩樣別的介面給不了的東西:**完整讀寫檔案的權限,和執行指令的權限**。也是因為這兩樣,它跑任何一條指令之前會先問你。 ## 終端機其實是兩層程式疊在一起 這裡有兩個程式疊在一起,而初學者通常怪錯那一個。**終端機**是負責顯示的那一層:它畫出一格一格的字,把你的按鍵轉出去,就這樣。它後面的 **shell**(zsh、bash、fish)等一行文字出現,跑那行文字,然後把程式寫出來的東西印在螢幕上。 就是因為薄到這個程度,agent 才操作得動。它打的每一行指令,跟你自己手動打的一模一樣;它讀到的錯誤訊息,跟你會看到的一字不差。 ## 螢幕上真正有意義的三樣東西 這份指南後面每一篇都假設你會看螢幕。真正承載意義的是三樣,其餘都是雜訊。 ``` yuki@mini ~/projects/auth (main) $ npm test ✓ src/session/refresh.test.ts (6 tests) ✗ src/session/expire.test.ts > drops a session after expiry Tests 1 failed | 41 passed (42) $ echo $? 1 ``` **提示字元**就是第一行。它告訴你四件事:你是誰、在哪台機器、哪個目錄、哪個分支。機器不在你眼前的時候,這一行就不再是裝飾。SSH 登入之後,它就是讓你分清楚這是自己的筆電、還是你忘記自己登進去過的那台伺服器。 **標準輸出**(*stdout*)是那個程式寫出來的文字。第二條串流 *stderr* 分開存放,真正的輸出不會跟抱怨混在一起。 **exit status。**每一條跑完的指令都留下一個整數,shell 用 `$?` 交給你。0 表示那個程式宣稱自己成功。agent 讀的就是這個數字。它讀的不是「1 failed」那些字,是那個 `1`,那是它的迴圈還沒閉上的訊號。 ## 介面決定 agent 能碰到什麼 網頁版的 agent 看不到你電腦裡的任何一個檔案,它只活在瀏覽器分頁裡。IDE 外掛看得到你打開的檔案,但通常停在「建議」這一步:它動不了 git,跑不了測試,更裝不了套件。 CLI agent 拿的是你自己那組權限:讀整個 repo、下 `git commit`、跑測試套件、裝依賴。存取分三層,風險一層比一層高: - **讀。**你家目錄下的每一個檔案,包含 `.ssh` 裡的金鑰。 - **寫。**在 git 管著的專案裡,改壞了一個 `git checkout` 就救得回來;專案之外,沒有東西會幫你 rollback。 - **執行。**任何你打得出來的指令,包括 migration、force push,還有指向錯誤路徑的 `rm -rf`。 這個差距沒有哪個廠商刻意設計過。選了終端機當介面,這組權限就跟著來了。別的介面都扣掉了一部分,所以也做不了同樣的事。 ## 哪些指令是沙箱裡的 agent 跑不了的? 「執行權限」是一個抽象的說法,點頭之前先把它還原成螢幕上的行: ``` npm test npx vitest run src/session/expire.test.ts git status --short git commit -m "fix: expire sessions on the dot" npm install date-fns@3 npm run build psql "$DATABASE_URL" -f db/schema.sql curl -sf localhost:8000/health | jq .db ``` 這些指令搬到別人的雲端沙箱裡,要跑在哪裡?那行 `psql` 連的是一個只存在於你機器上的資料庫,用的 URL 來自一個從來沒上傳過的 `.env`。那行 `npm install` 從你 `.npmrc` 裡寫明的私有 registry 拉內部套件。那些 `git` 動作發生在一個帶著你的 remote、你的 hook、你的 signing key 的 clone 上。 沙箱裡的 agent 可以寫一份 patch 給你,但看不見那份 patch 造成了什麼結果。看不見一次 edit 的結果,就沒辦法把工作做完。 ## 不需要你按任何鍵就會閉上的迴圈 你丟一句「把這個 bug 修好」。接著是十幾行同樣的指令,而你的手不用碰到鍵盤: - 它 grep 那條錯誤訊息,讀三個檔案,改一個。 - 它跑 `npm test`,讀 exit status,看到 `1`。 - 它縮到那一個失敗的案例,再改一次,整組重跑,拿到 `0`。 - 它 commit,在本地把 migration 套上去,再 curl 那個壞掉的 endpoint。 整條鏈上沒有一個按鈕需要按。換成瀏覽器分頁或 IDE 外掛,它在第二步就停下:它能產出那個 edit,卻跑不了測試,於是等你去跑,再等你把結果回報給它。到最後你變成了測試執行器。這是權限的問題,不是模型強弱的問題。 ## 為什麼位元組串比一個視窗值錢? 你螢幕上那個終端機視窗不是程式住的地方。程式接在一個 **pseudo-terminal** 上,那是 kernel 裡一個有兩端的物件:一端寫進去位元組,另一端就出來位元組,而你的視窗只是目前握著另一端的那個人。從這個定義跟著來三件事: - **可以存下來。**`npm test > build.log` 把那串文字寫進檔案,一年後另一個程式還能讀它。GUI 的畫布是一張圖,圖本身不是紀錄。 - **可以接起來。**`git ls-files | grep .env` 把一個程式的輸出,變成下一個程式的輸入。 - **可以搬走。**那個定義沒有一句話說兩端必須在同一個地方。tmux 把 session 擱在伺服器上,你的螢幕接上去、detach、再從另一台裝置接回來。 第三點才是這篇的重點。**位元組串可以被轉向、存起來、重播,也可以從別的地方接回去;渲染好的視窗做不到。**之後要講的持久 session、SSH、在手機上接回同一個 agent,每一件事都是從這個性質來的。 ## 完整存取要付什麼代價? 讓 agent 能對資料庫做 migration 的那個權限,也能把那個資料庫刪掉。它知道這一點,所以會在工作做到一半時停下來問:「要跑這條指令嗎?」那個 prompt 不是膽小。一個什麼都不問就直接跑的 agent,等於把你的 `.ssh` 目錄和正式環境的資料庫押在你身上。prompt 畫的是第二層跟第三層之間那條線,而且只能在那個時刻畫:指令已經知道了,還沒有執行。 兩個習慣擋掉大部分風險。**讀指令本身,不要讀摘要:**「我要清掉 build cache」是一段描述,只有它下面那一行才有後果。**同一類只批一次:**一天四十次要你准許跑測試,就用一條規則讓 `npm test` 直接通過。 這裡的代價不對稱。一個你本來就會准許的 prompt 花你十秒。一個你誤准許、跑在正式資料庫上的指令,花你一天和一次 restore。就是這個比例讓 AI 自動權限審查存在:先 blacklist,再 allowlist,剩下的交給模型判斷。 ## 什麼時候 GUI 比較好 終端機不是對 code 做任何事的正確介面。下面這些事情,用文字做就是比較差: - **即時 debugger。**設 breakpoint、單步走過那個迴圈、在第四個 frame 看一個變數。 - **任何視覺的東西。**排版、間距、一個差一點點的顏色。叫 agent 描述一張截圖,比你自己看一眼差得多。 - **讀一個大的 diff。**兩千行並排著看。所以就算 code 是 agent 寫的,你多半會在 GUI 裡 review 它的工作。 - **還在學的時候。**編輯器在你寫錯的那個 token 下面畫一條線,比一整牆的 stack trace 教得快。 這條分界分的不是熟練跟懶惰,分的是「你在場、你在開」跟「它在跑、你去看」。坐在桌前,IDE 常常是比較好用的那個工具,而把 CLI agent 用到最好的人,兩邊都留著。那條分工線怎麼畫,自己就是一篇。 終端機把 agent 放進你自己的機器,而它的輸出是可以住在任何地方的字元。所以那台機器不必是你正坐著的這一台。從別的地方連過去,是 SSH 的工作。 ## SSH:通往另一台電腦的隧道 URL: https://agentmux.saxcave.cc/guide/zh-TW/what-is-ssh/ Language: zh-TW Updated: 2026-11-06 SSH 是什麼、host/user/port 怎麼運作、為什麼金鑰比密碼省事,以及為何要用私有位址而不是對公網開 port。 SSH 是你手上這台裝置和另一台電腦之間的加密通道:你打的字送過去,那台機器執行,印出來的文字帶回來,中間經過的每一個節點都看不懂內容。要在手機上跑 agent,SSH 就是手機連到「agent 實際運行的那台機器」的那條路。 上一篇的結論是:agent 在終端機裡最能做事,因為它能讀整個檔案系統,也能真的執行指令。那個終端機不必在你口袋裡。它可以是家裡那台從來沒關過的 Mac mini,也可以是公司那台工作站,只要你的手機連得過去。 ## 為什麼是另一台機器,不是雲端服務? 網頁版 agent 和雲端 IDE 也會讓你連上一台機器。差別在那台機器是誰的。 SSH 連的是你自己那台。檔案在那裡,API key 在那裡,你裝過的套件和設好的環境變數也都在那裡。不用把任何東西搬到別人的伺服器上,改動留在原地,事後不必再同步回來。 反過來也有取捨。如果你沒有任何一台一直開著的機器,或是一整組人需要完全相同的起點,雲端環境是比較好的答案。換用自己的機器,代價是:它要一直開著、要連得到,而且設定由你負責。 ## 連線要打的只有三樣東西:host、user、port 每一次 SSH 連線都需要三個東西,指令把它們寫在同一行: ``` ssh -p 22 sax@mac-mini ``` | 部分 | 指的是什麼 | | --- | --- | | Host | 要連哪台機器:像 mac-mini 這種名稱,或 100.101.102.103 這種位址 | | User | 那台機器上的哪個帳號。你拿到的是那個帳號的檔案、權限和 shell,不會再多 | | Port | 那台機器上的哪扇門。SSH 預設是 22,所以這個欄位通常留空 | 卡住的人多數卡在第一項和第三項。host 必須是手機真的路由得到的位址,下一節就在講這件事。user 則必須真的存在於那台機器上:agent 以那個帳號執行,它能碰到的東西,剛好就是那個帳號能碰到的。 ## 為什麼不用 LAN 位址,也不用公網 IP? 你第一個找到的位址在網路設定裡,長得像 `192.168.1.23`。先從同一個 Wi-Fi 下的筆電試它,那正是它的用途。但它是路由器發出的私網位址,只在你自己的網路裡有效。手機切換到行動數據就沒有路能走到它,別人的網路也沒有。 第二種是家裡網路對外顯示的公網位址,再在路由器上做 port forwarding,把那個 port 指到這台機器。這條路走得通,也是要避開的那一條。掃段的機器人對全世界的位址全天候嘗試登入,一個對外開放的 port 一天之內就會開始累積登入嘗試。路由器的設定只要有一處錯了,暴露出去的是整個網路,不是一台機器。 該用的是第三種位址:只有你自己的裝置看得見的私網位址。Tailscale 會給每台機器一個 MagicDNS 名稱或一個 `100.x` 位址,公網上沒有別人知道它們存在。host 欄位就填這個,在家和在外都一樣。 ## 金鑰比較安全,也更省事 SSH 收密碼,也收金鑰。這件事常被講成安全和方便之間的取捨,但金鑰兩邊都贏。 一對金鑰就是兩個小小的文字檔。私鑰留在會發起連線的那台裝置上。公鑰由私鑰算出來,複製到你要連的那台機器,放進那個帳號 `~/.ssh` 裡的 `authorized_keys`。這條運算只有單向:公鑰從私鑰推得出來,反過來做不到。`ssh-keygen` 幾秒就產生一對,剩下的都是複製貼上。 登入時兩半互相證明配對,你一個字都不用打。密碼必須扛得住別人反覆猜,金鑰夠長,猜沒有用。設定一次,之後每一次連線都走同一套流程。 私鑰最好加 passphrase(金鑰口令),因為金鑰就是一個檔案。從一台沒鎖的筆電上把它複製走,別人就握有你的金鑰。加了口令,那個檔案沒有口令就毫無價值,而且口令跟帳號密碼不同:它從不過網路,只在本地解鎖金鑰。真正傳出去的,是對一個挑戰簽好的短答案。 在手機上,這個差別更重要。打出來的密碼每一次連線都要走過線路,任何摸得到那個 port 的人都多一次猜的機會。金鑰則是回答一個挑戰,金鑰本身從不離開你的裝置。如果金鑰都設好了還是被擋在外面,常見原因沒幾個:路徑錯、帳號錯、檔案權限錯。 ## App 把我的密碼和金鑰放在哪裡? Agentmux 不收集任何個人資料。使用統計是匿名、可選的,關掉它不會改變連線的方式。密碼、金鑰和口令都放進作業系統的硬體級憑證儲存,不是一般檔案。你的 SSH 流量不會被記錄下來:那條通道在你的裝置和你的機器之間加密,App 本身不是中繼站。 ## 第一次連線,為什麼要先問你信不信這組指紋? 連一台沒連過的機器時,在密碼或金鑰之前,你會先看到一行說無法確認主機真實性的訊息、一串十六進位字元,和一個 yes 或 no。那串字元是伺服器的指紋,也就是那台機器簽名用的 host key 濃縮出來的摘要。沒有出錯,這是 SSH 在驗證另一個方向。 密碼或金鑰證明的是「你對那台機器來說是誰」,host key 證明的是「那台機器對你來說是誰」。少了 host key 這半邊,任何一台站在你手機和家裡 Mac mini 之間的設備,都能冒充那台機器回答,把你送出去的每一個字讀走。第一次連線時你沒有存過的指紋可以比對,所以 client 請你先相信它,然後從那之後記住這個值。 這一步在之後才派上用場。重新連一台指紋變過的機器,client 會直接拒絕。要嘛是你重裝過系統,要嘛是有別的東西在代替它回答。 ## 一定要把電腦架成伺服器嗎? 很多人聽到 SSH 就想到機房和機櫃。你要做的只是打開一個開關。Mac 上是「系統設定 → 一般 → 共享 → 遠端登入」,Linux 上通常是啟動一個叫 `sshd` 的服務,多數發行版預設就裝好了。 開關打開之後,那台機器什麼都沒變。它還是你平常那台電腦,只是多了一扇可以從外面敲的門。不想記住順序的話,設定頁會問你三個問題,再依你的情況列出一張清單。 ## SSH 不是 VPN,也不是遠端桌面 SSH 不做下面三件事: | 它不是 | 那樣的話會是什麼 | SSH 實際做什麼 | | --- | --- | --- | | VPN | 你手機送出的所有流量都走進家裡網路 | 一條通道連到一台機器上的一個 port,其他東西不會因此連得到 | | 掛載的檔案系統 | 遠端的磁碟變成你能直接打開的資料夾 | 需要時才搬位元組:檔案瀏覽器得自己一個檔案一個檔案去抓、去傳 | | 遠端桌面 | 你看到整個桌面畫面,用手指去點 | 雙向都是文字:送出去的是按鍵,回來的是印出的文字 | 最後一列是真的限制,不是這個工具的缺點。只存在於「一個可以點的視窗」裡的軟體,SSH 操作不了。上一篇畫的是同一條界線:會用文字對話的 agent,才塞得進這條隧道。 然後你把手機收起來,隧道斷了,agent 跟著死。除非那個 session 本來就不住在你的連線裡面,那就是 tmux 負責的事。 ## 讓 agent 在你關掉手機後繼續跑 URL: https://agentmux.saxcave.cc/guide/zh-TW/what-is-tmux/ Language: zh-TW Updated: 2026-11-06 tmux 是什麼,為什麼 SSH 斷線時你的 AI coding agent 不會死:session 留在伺服器上,手機只是接上去看。 tmux 是一個 terminal multiplexer:它讓 terminal session 繼續跑在你連進去的那台機器上,不是跑在你那條連線裡面。對 AI coding agent 來說,這就是斷線之後 agent 能不能活下來的差別。SSH 連線遲早會斷(手機鎖定、沒訊號),而斷掉的 SSH 通常會把 agent 所在的 shell 一起殺掉。 你把手機收起來,SSH 隧道斷了,agent 跟著死。這不是運氣問題,是機制。 連線一斷,kernel 會關掉你這次登入所用的虛擬終端機(pseudo-terminal),再對接在那個終端機上的每個 process 送出 **SIGHUP**(hangup):你的 shell,也包括 shell 裡面的 agent。process 除非自己處理過這個訊號,否則預設就是終止,跑到一半的東西全部作廢。SSH 只是連線走過的那條路,撐住 agent 的是那台機器,不是那條連線。 ## tmux 讓 session 活在別的地方 tmux 在伺服器上開一個 session。你的 agent 跑在那個 session 裡面,不是跑在你那條一直會斷的 SSH 連線裡。 背後的機制是一個 process。你第一次輸入 `tmux`,它就在你的帳號底下啟動一個背景程式,那是 tmux *server*,會一直活著。之後你開的每個 session 都屬於它,每個 session 的虛擬終端機也由它分配,不是由 `sshd` 分配。你的 agent 用的那個終端機從此刻起歸 tmux server 保管,跟手機那條連線沒有關係。 下次連線斷掉的時候,`sshd` 關掉的是它自己的虛擬終端機,掛在那上面的只有你的 tmux client。client 結束了,session 照樣跑。 ## session、window、pane 各是什麼? 這三個詞常常被混著用,但只有第一個能保住你的 agent。後面每一段都用得到這三個詞,所以先把它們分開。 | 詞 | 是什麼 | 對 agent 的意義 | | --- | --- | --- | | session | 機器上一個有名字的工作場所,自備 process,由那台機器上的 tmux server 保管 | 斷線之後活下來的就是它。名字取一次,可以用好幾個月 | | window | 一個 session 裡面的一個分頁,多數人一個 agent 開一個 | 你切走,它在後面照樣跑 | | pane | 一個 window 裡面的分割:左邊 agent,右邊測試或 git status | 手機那一小塊螢幕上同時看到兩件事 | 第四個詞也有用:**client** 就是當下正在看這個 session 的終端機。一個 session 可以沒有 client、有一個、或有好幾個。零個不是錯誤,那就是 detach:session 照樣在跑,只是沒人看。 名字因此很重要。`tmux new -s work` 建立的 session 一週後還找得回來;不取名,tmux 就幫你編號,而「接回第 2 號」這件事過了三天就沒有意義。 ## detach、attach,跟關掉電視是同一件事 把電視關掉,客廳裡的畫面沒了。節目沒停,一直在播。你回來打開,接的是同一個頻道,從它播到的地方看下去。 tmux 把這兩個動作叫 **detach** 和 **attach**:關窗、開窗。兩個都碰不到機器上真正在跑的東西。 這個比喻會漏掉一件事,而且漏掉的部分對你有利。電視關著的時候什麼都沒學到,tmux 的 session 卻把輸出留了下來。一個小時後接回來,畫面是它現在的樣子,那一個小時印出來的東西還在 scrollback 裡,往上翻就看得到。 ## 要下哪些指令? 先記這四個: ``` tmux new -s work # 建立一個名叫 work 的 session claude # 在裡面啟動 agent tmux ls # 列出 session:work: 1 windows tmux attach -t work # 從任何終端機再接上去看它 ``` **detach 不是指令,是按鍵。**按 Ctrl-b,放手,再按 d。前面那個鍵是 *prefix*:tmux 收下它,下一個鍵才決定要做什麼。另外兩個第一週就會用到: - `Ctrl-b d` 就是 detach。終端機變回一般的 shell,session 繼續跑。 - `Ctrl-b c` 開一個新的 window;`Ctrl-b %` 把一個 window 分割成兩個 pane。 如果機器回你 `tmux: command not found`,解法在疑難排解那一頁。 ## 連線斷掉,agent 繼續跑 你在捷運上斷了訊號,進電梯斷一次,切 Wi-Fi 又斷一次。每次都接回同一個 tmux session,斷線十次,agent 一次都沒有被打斷。它不知道你斷過線,因為它從頭到尾就沒活在你那條連線裡。 手機上三個最令人擔心的情況,其實是同一件事發生了三次: - **手機鎖定。**系統把 app 暫停,連線跟著斷掉,什麼訊號都送不到 agent。它繼續做事。 - **二十分鐘沒訊號。**連線在背景安靜地死掉,tmux client 跟著死,session 只是被 detach,內容一點沒變。 - **系統殺掉 app,或你往上一劃。**沒了的是手機上的一個 process,session 從頭就不在裡面。在筆電上關掉終端機視窗,結果一模一樣。 detach 不是暫停。你不在的時候,agent 繼續讀檔案、改檔案、花掉你的用量。這就是重點,所以走開之前先回它一句,或是先把它停掉。 ## 兩台裝置可以同時看同一個 session 嗎? 可以,而且沒有什麼魔術。多個 client 可以同時接在同一個 session 上。排隊時從手機開工,到公司從筆電接回同一個,你看到的是跑到一半的同一個畫面,不是它的副本。 兩邊按的鍵都送進同一個程式,所以一次只有一個人打字。attach 不會移動 session:它留在建立它的那台機器上,所以手機和筆電看到的內容一致。反向也成立:你在筆電的終端機裡手動開的 session,app 一樣接得上,因為兩邊問的是同一台機器上的同一個 tmux server。 ## App 怎麼處理這些 session? Agentmux 用的就是上面那個區分:session 在機器上,連線只是去看它。手機這一側的斷線是 detach,不是停止:重新連線,你回到同一個 session,而它已經跑得更遠了。你在它的終端機裡手動下上面那些指令,結果一樣,因為那是同一台機器上的同一個 session。 有兩件跑腿它幫你做掉。機器上沒有 tmux 時,app 會自己裝,並判斷你的機器用的是 apt、yum、dnf、pacman、apk 還是 brew。agent 裝在 shell 看不到的地方時,app 會去查那個 PATH 問題,不會只丟給你一個毫無訊息的畫面。persistent session 屬於免費的 single-server 方案,所以上面這些都不用另外付錢。設定頁把整件事變成一份檢查清單。 ## tmux 不做的事 tmux 只回答一個問題:這個 process 會不會活得比你的連線久。其他它一概不管。 - **伺服器重開,所有 session 一起結束。**它們存在 tmux server 的記憶體裡,一次斷電、一次自動更新,都會把 agent 一併帶走。活下來的是已經寫進磁碟的東西:改過的檔案、做出的 commit、你停在哪個 branch。負責存檔的是 git,不是 tmux。 - **它不會還原 agent 已經跑過的指令。**tmux 留住的是輸出,檔案不會自己回到之前的樣子。 - **`tmux kill-session -t work` 真的會殺掉它。**關掉最後一個 window,或是 agent 自己崩潰,結果一樣。`tmux kill-server` 則把所有 session 一併帶走,而且 tmux 不會先問你。 - **它不會讓機器保持醒著。**整晚開著的 Mac mini,跟闔上蓋子就睡的筆電是兩回事:筆電睡著,agent 跟著暫停,真的斷電的話,session 就會整個消失。 兩分鐘就能驗證整篇。SSH 進去,跑 `tmux new -s work`,啟動 agent,按 Ctrl-b 再按 d,把連線整個關掉,然後跑 `tmux attach -t work`。畫面還是同一個,已經跑得更遠,上面有一個問題在等你回。 agent 現在能在你收起手機之後活下來。剩下一個問題:怎麼從外面連進去,又不想在路由器上對公網開任何一個 port。Tailscale 回答的是這個。 ## 不要對公網開 port URL: https://agentmux.saxcave.cc/guide/zh-TW/what-is-tailscale/ Language: zh-TW Updated: 2026-11-06 不開 port 也能從手機連回去:WireGuard mesh、MagicDNS 名稱、100.x 位址,什麼時候不需要 Tailscale。 在家裡的電腦和手上的手機都安裝 Tailscale,兩邊登入同一個帳號,接著 SSH 連線時填那台機器的 MagicDNS 名稱或 `100.x` 開頭的位址。Tailscale 在你自己的裝置之間建立 WireGuard 加密 mesh,所以不需要對公網開放任何 port,也不需要一個固定的公網位址。 agent 在你關掉手機之後還能繼續跑,但光是這樣還不夠,你得先有一個安全的入口可以連回去。最直覺的做法是把家裡的 22 port 開到公網,而那也最快出事:掃描機器人對全世界的位址全天候敲 22 port,失敗的登入記錄不到一天就會堆起來。 第二個問題比較無聊,也更難避開。你家的對外 IP 幾乎一定是動態的,每隔一陣子換一次號碼;機器又躲在 NAT 後面,跟家裡其他裝置共用那一個對外位址。那裡本來就沒有公網位址可以開。 ## 傳統解法是路由器上的三件事 以前的做法是自己進路由器打通:port forwarding 把公網某個 port 轉給家裡那台機器,DDNS 把主機名稱綁到當下那個 IP,或是向 ISP 買固定 IP,然後自己維護防火牆規則,擋掉除你以外的每一個人。 每一步都要對。錯一步,要嘛連不進去,要嘛整個家用網路暴露在外。就算全部設定正確,那扇門也永遠開在公網上,你在不在家都一樣。 ## port forwarding 加動態 IP 為什麼一直出包? 這裡有四個會動的零件,每一個都可以在最不方便的時候鬆掉。 - **位址會變。**ISP 重新連線就換號碼,DDNS 客戶端要幾分鐘才跟上。每次換號碼之後都有一段時間,你存好的那個名稱連不回家;如果那次更新發生在你出門時,名稱從此指向別人的網路。 - **那個位址常常不是你的。**躲在 carrier-grade NAT 背後時,路由器只拿到 ISP 內部網路的一個位址,port forwarding 因此完全無效:對外那個位址屬於 ISP,而且是好幾個使用者共用。 - **多一個轉發就多一扇門**,門後面是家裡的網路,裡面有智慧電視,也有舊攝影機。 - **它必須永遠正確。**韌體更新會清掉規則,換路由器要把規則全部重建,機器換了區網位址,轉發就指向空處。 最麻煩的是出錯那一刻:你在月台上,用行動網路的手機去檢查家裡的路由器,而你要連的東西剛好連不到。 ## Tailscale 做了什麼? Tailscale 是一個 WireGuard mesh。每台裝置都裝好、都登入同一個帳號,裝置之間就會直接建立一條加密連線。這麼做沒有一個對外入口,公網上也沒有 port 等著被掃描。 每台裝置產生一對 WireGuard 金鑰,私鑰那半從不離開裝置。公鑰拿去互相交換,交換完成,隧道就建立了。帳號那一側是帳務工作:讓裝置彼此認識、記下每台裝置現在從哪裡連得到、發出公網不會路由的私有位址。 MagicDNS 給每台裝置一個你記得起來的名稱,名稱解析由 Tailscale 自己處理,你不用碰那個會變的 IP。裝置在 NAT 後面、位址昨晚換過、從家裡的 Wi-Fi 切到行動網路,這些變化它在背後就處理好了。 ## 手機也在 NAT 後面,mesh 為什麼還行? 行動網路讓問題更糟。手機也沒有可以被連進來的公網位址:在 4G、5G 之下,它躲在 carrier-grade NAT 後面,跟大量使用者共用同一個對外位址。所以靠別人從外面敲門的做法,兩端同時失效。 mesh 是反過來的設計:兩邊都只需要往外送封包。手機送一顆,機器送一顆,雙方各自知道對方看起來在哪裡,再試著穿過自己的 NAT 開出一條直連。加密由 WireGuard 負責,介紹由 mesh 負責。找不到直連時,流量改走中繼站,連線不會斷,也仍然是端到端加密。 ## 你的機器多了一個只有你連得到的位址 你家那台機器多了一個私有位址。只有登入同一個 Tailscale 帳號的裝置看得到它、連得到它。掃描機器人不知道它存在,因為它從沒出現在公網上,而你的路由器什麼都沒改。 從這裡開始,你的 SSH 連線打的就是這個位址,不再是那個天天被人敲門的 22 port。 ## 你該打的是哪個位址? 區網那個不是答案。同一台機器在這個架構下同時有三個位址,只有兩個值得存進 app。 | 位址 | 在哪裡連得到 | 要不要用 | | --- | --- | --- | | 區網位址,例如 192.168.1.23 | 只有那個 Wi-Fi 底下 | 不要。手機切到行動網路就沒有路走到它 | | ISP 給路由器的位址 | carrier-grade NAT 之下哪裡都不通 | 不要。它會變,而且是路由器的 | | MagicDNS 名稱,例如 mac-mini.tail1234.ts.net | 任何網路,兩端隧道都開著就行 | 要。存這個 | | 100.x.y.z | 跟名稱一樣 | 留一組,名稱解析壞掉時用 | 區網位址代表的是那個區域網路,不是你那台機器。只要存名稱,機器換 Wi-Fi、換 IP、甚至搬家,你都還連得到。在機器上打 `tailscale status`,兩個值都會列出來。 兩台裝置看起來都已經連線,SSH 卻還是失敗的話,先查你填的那個位址。Tailscale 顯示已連線,SSH 仍連不到那一節寫了完整的順序,包括機器被登出、需要重新登入的情況。 ## 這樣做你要付出什麼? 有三筆成本,最好在你擠進捷運之前就知道。 **機器上多一個常跑的程式。**它隨開機啟動、偶爾自己更新、也有自己的 bug。機器連不到的時候,要多排除一個嫌疑犯,才輪到 SSH。 **多了一個控制面,而你在最壞的時刻需要它。**流量直接走裝置對裝置,但裝置之間的介紹不是這樣。新的筆電、剛換掉的手機、過期的憑證,在它們重新登入之前都不在你的網路裡。如果那次重新登入發生在你出門時,機器好好的,你就是連不到。另外留一條不經過它的進入方式。 **手機必須維持連線。**手機上那條隧道是一個 VPN profile,手機會把它關掉:重開機之後、別的 VPN 接手之後、還有你點掉那個要求的時候。除錯之前先確認它還開著。它只承載連往你自己那些位址的流量。 ## 私人網路不是改回密碼的理由 有一個看起來合理的推論:公網連不到的機器,門上不必上鎖。這個推論錯了,那台機器還是要上鎖,而且有兩個理由。 mesh 的安全現在就是帳號的安全。任何能登入的裝置都在網路裡面,也就是多了一台你已經信任的裝置。忘在計程車上的筆電不是掉在公開的 Wi-Fi 上,是在你自己蓋的網路裡面,每台機器都連得到,也沒有別的東西攔它。金鑰的好處是那一台裝置還是得證明自己是誰,才走得到下一步。 第二個理由是多一層防線:mesh 讓外面的人進不來,金鑰讓已經在裡面的陌生人走不下去。兩件獨立的事情都失效,才會有人動到你的專案。密碼是一個猜得中的東西,金鑰不是。金鑰怎麼完成證明,寫在 SSH 那一篇。 ## 什麼時候不需要 Tailscale? 如果機器和手機永遠都在同一個 Wi-Fi 底下,跳過這一步。區網位址就能用,不用裝東西,也不用登入帳號。缺點是你一走出家門就全部失效,而這篇處理的正是出門之後。 自己租的伺服器是另一種情況。位址不會動,沒有路由器要設定,玩壞了也不會把自己鎖在外面。換過來的風險是 22 port 永遠面對公網。有人還是會在上面跑 mesh,把 SSH 收到私有位址後面;有人讓它維持公開,完全依賴金鑰。兩種都可行,不可行的只有一種:暴露在公網上的 port 用密碼登入。 如果你已經透過別人管理的網路連到那台機器,例如公司的 VPN,就走那條路。兩條重疊的隧道不會讓你更容易連上,只會讓除錯更難。如果你不確定自己屬於哪一種,設定頁會問三個問題,然後回給你一張排好的清單。 進門的路解決了。下一個問題在你手上那台裝置:它假設你是來做什麼的。 ## 為什麼不是 Termius / Blink URL: https://agentmux.saxcave.cc/guide/zh-TW/why-agentmux/ Language: zh-TW Updated: 2026-11-06 Agentmux 對 Termius、Blink:五個功能差異,以及什麼時候選 Termius 比較好。 通用的 SSH app 假設你打開它是為了打指令;Agentmux 假設你是要接回一個已經在跑的 agent。需要 Mosh、SOCKS5 轉發,或跨平台同步主機清單,就買 Termius 或 Blink。如果你整天在好幾台伺服器的 agent 之間切換,就買 Agentmux:一個贏在範圍,一個贏在接回的深度。 兩個都能開 SSH、都能打指令,分開的地方只有一處:誰把「接回」這件事想得比較透徹。底下每一條差別都是從那個假設來的,五個功能全部對照現行的程式碼查證過。 ## 兩種工具的核心假設不同 通用的 SSH client 圍繞的是「一台主機加一個 shell」:給你一個能用的 prompt,然後別擋路。prompt 之後發生什麼是你的事:你開哪個 process、它活不活得下來、明天怎麼找回來。 Agentmux 圍繞的是「一個已經在跑的 agent」,它的任務是縮短你口袋到那個 process 的距離。三個結論跟著來,後兩個是通用的 client 補不起來的地方。原因不是它做得比較差:那些行為本來就不在它的產品範圍裡。 - **你先看到什麼。**一台主機,或這台主機上跑著的 session。 - **斷線的代價。**一個要自己重新走的 prompt,或你離開時的那個畫面。 - **你不在的時候 app 做什麼。**什麼都不做,或鎖定螢幕上的進度,加上一聲跑完的提示音。 ## 每個功能擺在一起,誰做得到? 每一列一個功能。對手從外面查證不到的行為,格子裡就寫查證不到,不猜。 | 功能 | 通用 SSH client | Agentmux | | --- | --- | --- | | 接回 tmux session | 自己打 tmux ls,再打 tmux attach -t | 連上就把伺服器上現有的 session 列出來,點一下接回 | | 斷線之後 | prompt 回來了,session 要自己找 | detach 不是死亡:你落在離開時那個畫面 | | 跑幾十分鐘的 agent | 沒有「一次執行」這層概念可以回報進度 | 動態島與鎖定螢幕顯示進度,跑完可以播提示音 | | 問你要不要准一個指令 | 你人必須坐在螢幕前 | AI 自動權限審查:黑名單擋掉、白名單放過、其餘交給 AI 判斷,再給建議或幫你按(Pro) | | 改檔案裡的一行 | 用手機鍵盤跟 vim 肉搏 | Runestone 編輯器、Ink 雙欄 Markdown 預覽、分支工具、Files app 整合(Pro) | | 私鑰放在哪裡 | 從外部查證不到 | Keychain/Keystore,絕不寫進本機資料庫;iCloud 同步只帶走伺服器名稱 | | 從 app 外面自動化 | 沒有 session、專案、agent 這層模型可以交給系統 | 6 個 App Intent,涵蓋四種 entity:Server、Project、TMUXSession、RecentSession | | 不穩的行動網路 | Mosh 是買這類 app 的理由 | 只有標準 SSH 加 tmux 自動重連,不等於 Mosh | | Port forwarding | Local、Remote、Dynamic(SOCKS5)都有 | 只有 Local | | 大規模的主機設定 | 五個平台同步,主機清單能共享給團隊 | 多伺服器與批次操作(Pro),平台只有手機與平板 | 先看最後三列。Agentmux 就是在那裡輸的,那也決定了哪些讀者該留在通用工具那一邊。 ## 為什麼要先列 tmux session? 連上伺服器,Agentmux 直接把上面現有的 tmux session 列出來,點一下就接回去。不用記 `tmux ls`、`tmux attach -t`。斷線之後重連,接的還是同一個 session,畫面停在你離開的那一刻,不用重新打一次 attach。 agent 的價值在它累積下來的脈絡:讀過哪些檔案、為什麼選這個做法。接回同一個畫面,脈絡還在眼前;開一個新的 prompt,你得對一個已經忘光的 process 把任務重講一次。如果你還沒用過 tmux,先讀tmux 在做什麼,這整套設計都建立在它上面。 ## 你不在的時候,app 在做什麼? agent 常常一跑就是幾十分鐘,這個功能就是為那段等待做的:動態島和鎖定螢幕顯示目前進度,跑完可以播一聲提示音。App 不必開在最前面,讓你死盯著等結果。 圍繞 shell 的 app 關掉之後沒有東西可回報,因為它沒有「還在跑」這層概念。這邊是某台伺服器上某個有名字的 session 在跑,系統的面板替它回報。等它停下來問你要不要允許某個指令時,AI 自動權限審查可以先替你看一眼:危險的擋下來,安全的給建議或直接幫你按(Pro)。 ## 在手機上改檔案,一定要開 vim 嗎? 臨時改一行程式碼,不必在手機上跟 vim 肉搏。內建的 Runestone 編輯器直接開檔案讓你改。要看 agent 產出的 README,Ink 提供雙欄 Markdown 預覽,原始碼和排版同時看得到。Git 工具(切換分支、查 diff)和 Files app 整合是 Pro 功能,14 天試用期間也用得到。 重點是整個循環在一台裝置上閉合:agent 改檔案,你看 diff,你回答,它繼續跑。每多一個步驟要換到另一台機器,你就多跑一趟書桌。 ## 你的私鑰放在哪裡? 私鑰在 iOS 只存在系統的 Keychain 裡,在 Android 則由 Android Keystore 做硬體保護,不會寫進本機資料庫。如果你開了 iCloud 同步(iOS),CloudKit 帶走的是伺服器名稱這類不敏感的 metadata。私鑰留在原地。 考慮任何工具都問同一個問題:開了同步之後,我的哪些私鑰會離開這台裝置?功能清單不會回答你。 ## Siri 可以把終端畫面交給捷徑 對 Siri 說「從 Agentmux 取得目前終端內容」,`GetCurrentSessionContextIntent` 就把終端當下的內容抓進 iOS 捷徑,可以接去記事本、訊息,或任何你自己組的流程。`ContinueLastSessionIntent` 更直接:一句「在 Agentmux 繼續工作」就接回上一個 session。 這類自動化,通用的 SSH app 結構上做不到:它們沒有 session、專案、agent 這一層語意模型可以交給系統。Agentmux 目前實作 6 個 App Intent,涵蓋 Server、Project、TMUXSession、RecentSession 四種 entity。 ## 三個只有繁中使用者會有感的修正 1.3.10 修掉三個英文圈使用者感覺不到、繁中讀者一眼就懂的毛病:全形字選取時對齊跑掉,連結跨行時複製出來斷成好幾截,詞句裡出現一個斜線就被當成檔案路徑。沒有實際處理過中日韓文字的人,不會踩到這三個坑。 ## 你的工作是哪一種,就買哪一種 **主要是一台主機上的手動終端工作。**你登入是為了跑自己挑的指令:重啟服務、tail log、看磁碟。那就買通用的 SSH client。想先試試以 session 為中心的做法,免費的單伺服器方案就涵蓋一台伺服器、持久 session 和核心終端機。 **整天住在好幾台伺服器的 agent 裡。**一個在 Mac mini 上重構,另一個在 Linux 機器上跑測試,第三個是你上火車前開的。這就是 Agentmux,而且具體來說是 Pro:多伺服器、自訂 agent、遠端編輯、檔案歷史、分支工具、批次操作。 **agent 活在瀏覽器分頁或雲端沙箱裡。**方便是真的,代價是檔案和憑證不再存在你的機器上。Agentmux 從不代跑你的工作:它是 client,程式碼和 agent 留在你自己擁有的硬體上,一台 Mac、一台 Linux 機器、或一台伺服器。把專案和金鑰放到別人租給你的環境裡,只要你覺得安心,那也是合理的選擇。 怎麼連進去跟選哪個 app 一樣重要。標準 SSH 加金鑰或密碼就夠,建議走 Tailscale,因為不用對公網開 port。位置要用 MagicDNS 名稱或 100.x 位址,不是區網 IP。 ## 什麼時候該買 Termius 而不是 Agentmux 下面任何一條如果是你的主要工作,就去買通用工具。 - 你在很不穩定的行動網路下工作,需要 **Mosh** 撐住連線。Agentmux 只有標準 SSH,靠 tmux 自動重連補回來,但那不等於 Mosh。 - 你需要 **Remote/Dynamic(SOCKS5)port forwarding**。Agentmux 只有 Local。從某個網路打隧道出去、或叫瀏覽器走跳板主機,是你每天在做的事。這樣的話,這是硬缺口,不是偏好問題。 - 你要在 iOS/Android/Windows/macOS/Linux 之間同步大量主機設定,或跟團隊共享主機清單。Agentmux 是手機與平板的 client:Pro 有多伺服器與批次操作,沒有共享給同事這回事。 - 你是 SRE 或系統管理員,要在幾十上百台伺服器上做維運。能被腳本操作的主機清單,勝過一個要你點來點去的 session 清單。 Agentmux 為一個工作流設計:**在手機上接回正在跑的 coding agent。**它不是通用的維運工具。如果你一週的工作就是一條一條零散的指令,這些差別對你沒有價值。 決定用 Agentmux?下一個問題是連線另一頭那台機器。設定精靈問三個問題,然後給你一張檢查清單;之後常見的毛病,疑難排解已經都寫在那裡了。 ## 15 分鐘,讓你的 agent 開始整晚工作 URL: https://agentmux.saxcave.cc/guide/zh-TW/setup/ Language: zh-TW Updated: 2026-11-06 回答三個問題,拿到照你的答案生成的步驟清單:機器、要跑的 agent、從哪裡連回來。 把 agent 放到遠端整晚跑,大約 15 分鐘:用 SSH 連進你自己那台機器,用 tmux 讓手機斷線時只是 detach、不會停止 agent,再用 Tailscale,就不用對公網開 port。回答三個問題:你的機器、要跑的 agent、你想從哪裡連。清單會照你的答案生成。 每一步都會告訴你為什麼要做、指令是什麼、怎麼確認成功了,還有沒成功的話去哪裡看。(那台機器是 Windows?先看這一節。) 這個安裝精靈需要 JavaScript。你也可以直接看疑難排解頁。 ## 原生 Windows 沒有 tmux,改走 WSL2 你選了 Windows,所以精靈沒有給你清單。Windows 這條路跟另外三種走法不一樣,塞給你半套清單反而害你。 Windows 走得通,但要經過 WSL2,比 Mac 或 Linux 多幾道手續。如果你只是想讓 agent 今晚就開始工作,去租一台最便宜的雲端 VPS,照上面那條路走。那通常比在 Windows 上打通還快,也幫你省掉一個晚上。 ### 為什麼原生 Windows 不行? 這份指南靠三個零件:CLI、tmux、SSH。SSH 沒問題,Windows 10 和 11 都內建 OpenSSH Server,打開就能連進去,拿到一個 PowerShell 提示字元。 問題出在 tmux。**它沒有原生 Windows 版本**,而它是整份指南的承重牆。少了它,手機連線一斷,agent 收到 SIGHUP 就死(機制在這裡),「關掉手機它還是繼續跑」這句話直接不成立。你會多出一台能 SSH 進去的 Windows,但那不是這份指南要給你的東西。 agent 本身在 Windows 上跑不跑得起來,各家不同、變動也快,這裡刻意不列清單,請查你要用的那個工具的官方文件。但順序要分清:**agent 在原生 Windows 上跑得再好,也碰不到 tmux 那個問題。**能不能跑是一個問題,斷線之後活不活得下來是另一個。 ### 建議的做法:WSL2 WSL2 裡面是一個完整的 Linux,tmux、sshd、agent 的行為都跟真的 Linux 一樣。裝好一個發行版,前面「Linux 主機」那條路的每一步都適用。回到上面,第一題改選「一台 Linux 主機」。 ### WSL 有三個 Mac 和 Linux 沒有的坑 三個都解得掉。先知道,卡住的時候就不會以為是自己弄壞了什麼: - **網路。**WSL2 跑在一台虛擬機裡,那台虛擬機有自己的 IP,而且躲在 NAT 後面,所以預設情況下,從那台 Windows 外面連不到裡面的 sshd。有兩種解法。一是在 Windows 端用 `netsh interface portproxy` 把 port 轉進去,但 WSL 的 IP 每次重開都可能變,你等於要一直重設。二是在 `.wslconfig` 開鏡像網路模式 `networkingMode=mirrored`,讓 WSL 直接共用主機的網路介面,port 轉發這一步整個消失。第二種乾淨很多,但需要比較新的 Windows 11。 - **WSL 不會自己一直開著。**最後一個程序結束,發行版幾秒之內就關掉,虛擬機也跟著關。你要的是一台*常開*的機器,所以 WSL 得在開機時就跑起來,沒人登入也留著,通常靠排程工作或 keepalive 撐住。Mac 和 Linux 上沒有這層問題。 - **systemd 要自己開。**舊的 WSL 根本沒有 systemd,`systemctl` 這條指令不存在,所以精靈裡「在 Linux 主機上啟用 SSH 服務」那一步給的指令會直接失敗。新版可以在 `/etc/wsl.conf` 加 `systemd=true` 打開,但預設是關的。 ### 如果 Windows 只是你工作用的那台電腦呢? 這裡講的 Windows,指的從來是「那台被連進去的機器」。如果你的 Windows 只是你平常工作的電腦,agent 跑在別台上(家裡的 Mac mini、公司的 Linux 主機、一台 VPS),那上面這些你一個都不會遇到,這份指南對你完全成立。你要設定的機器,不是你手上這台。 ## 在 iPhone、iPad 上跑 Claude Code URL: https://agentmux.saxcave.cc/guide/zh-TW/claude-code-on-iphone/ Language: zh-TW Updated: 2026-11-06 Claude Code 跑在你自己的機器上,手機只負責終端機。遠端登入、Tailscale、tmux,還有 PATH 的坑。 可以,但 Claude Code 不會跑在你的 iPhone 上。它跑在你自己擁有的 Mac 或 Linux 機器上,手機只負責帶著終端機。做法是:打開遠端登入,用 Tailscale 取代對外開放的 port,開一個有名字的 tmux session,在裡面啟動 `claude`。鎖定螢幕之後,agent 照樣繼續跑。 「在手機上跑 Claude Code」這句話聽起來很直白,實際上講的是兩台機器、兩種工作。把它們分開之後,問題都變短了:斷線之後什麼還會活著、為什麼同一條指令在 SSH 裡找不到、agent 能不能在你搭捷運的時候自己回答權限提示。 ## Claude Code 是跑在手機上嗎? Claude Code 是你某台機器硬碟裡的一個程式,由 shell 啟動。它透過那台機器的檔案系統讀你的專案,跑 `git`,跑測試,把改好的內容寫回同一個目錄。很容易以為它有一部分現在住在你口袋裡,但上面這些事沒有一項發生在手機上。 從手機連過去,線上只跑兩樣東西:你按的鍵出去,session 印出的字元回來。手機上沒有裝任何 agent 的元件,你的 repo 也不必搬到任何地方。手機顯示的,只是另一台電腦上那個 session 現在的樣子。 - **手機上。**按鍵、畫面、你的拇指。鎖定螢幕、把 app 滑掉、走進電梯,停掉的只有畫面。 - **機器上。**process、正在被改的檔案、正在跑的測試、它已經讀過和決定過的一切。你的螢幕變暗,這一切都不會停。 有兩個後果。機器必須一直開著、而且要連得到:蓋著蓋子收在抽屜裡的筆電什麼都不會跑。手機那一邊最便宜,換一支手機就恢復了。 ## 先決定的三件事,順序不能換 少任何一件都不成立,所以照這個順序做。 - **一個進得去那台機器的門。**Mac 上是「系統設定 → 一般 → 共享 → 遠端登入」;Linux 上是 SSH 服務,一個叫 `sshd` 的 daemon。 - **一個私有網路,不要對外開放的 port。**機器和手機都裝 Tailscale,登入同一個帳號,然後連那台機器的 MagicDNS 名稱或 `100.x` 位址,不要連 `192.168.1.23` 這種區網位址,因為手機切到行動網路之後,那個位址什麼都不是。另一條路是把 port 22 轉向公網,那等於把機器攤在公網上,誰都能過來試你的密碼。 - **一個有名字的 tmux session,而且 agent 在裡面。** ``` tmux new -s work # 取名為 work claude # 在 session 裡面啟動 ``` 之後從手機、從另一個網路接回去的時候: ``` tmux ls # 現在有哪些 session tmux attach -t work # 開一個窗看進去 ``` 名字不是裝飾:沒有名字,你就沒有可以指名要的那個 session,只打 `tmux attach` 會接上最近的一個,那是在猜你的意思。機器上還沒有 tmux 的話,Agentmux 會自己裝。 ## 只用一個 SSH 視窗,鎖定螢幕那一刻 agent 就死了 假設你跳過 tmux:從手機 SSH 進去,跑 `claude`,看它開始讀專案,然後把手機放進口袋、鎖定螢幕。 手機不再送封包,連線逾時、關閉。機器上的 sshd 跟著關掉那條連線所使用的 pseudo-terminal,kernel 對還掛在上面的每一個 process 送出 **SIGHUP**:你的 shell,還有 shell 裡面的 Claude Code。它們預設的反應就是結束。agent 停在改到一半的地方,之前讀了二十分鐘的專案也一起沒了。 讓 app 開著救不了它:Wi-Fi 換一個網路、行動網路斷訊、一個 Ctrl-C 按錯地方,連線都照樣斷。tmux 把 session 從連線上搬到機器本身,所以你斷線時消失的是窗,不是 process(機制在那篇 tmux 文章)。結論只有一條:**agent 要在開始做事之前就在 session 裡面,因為一個正在跑的 process 沒辦法之後搬進去。** ## 用手機工作實際上是什麼樣子? 手機適合一次只做一個決定,而 agent 的工作循環就是這樣:它提出一個計畫,你用一句話核准或打回;它問要不要順便處理 migration,你選第二個。你回兩行修正:「serializer 不要動,export 那支在用它」,然後把手機收起來。 螢幕鍵盤是第一筆真實的成本。backtick、pipe、引號、反斜線、代表家目錄的 tilde,全部藏在兩層之後,所以 `~/src/auth/session.ts` 這種路徑打起來比想像中久,打錯一段就是一個來回。長的 prompt 在手機上比較難寫也比較難檢查,很多人先在備忘錄裡寫好,再貼進來。 讀 diff 是手機真正不夠用的地方:兩個檔案改了四十行,加上周圍的 context,在六吋螢幕上就是兩三個畫面的捲動;而且換行會讓你看不出某個改動落在行尾還是行中,而那正是你要 review 的東西。把 iPad 橫過來,同一個 diff 就不用換行。 一個馬上回本的習慣:把吵的部分放進第二個 session,測試就不會蓋掉 agent 自己的提示。 ``` tmux new -s tests -d # -d 建立後立刻 detach tmux attach -t tests # 看完再 detach ``` ## agent 可以自己回答權限提示嗎? Claude Code 不是想到什麼就跑。它跑每一條指令之前都會先停下來;依照設定,改檔案之前也會停。它把問題擱在畫面上:這是它要跑的指令,可以嗎?工作就在這裡等,而如果只有你能回答,要等多久就看你什麼時候抬頭。離開書桌之後,權限提示不再是不方便,而是整件事的瓶頸。 AI 自動權限審查只做這件事:讀畫面上目前那一則提示,然後給你建議、或幫你按下那個鍵。檢查有固定的順序。**黑名單**擋掉危險指令,命中的一定交給你。**白名單**放行你原本就信任的指令,直接在裝置上決定,不用等。**AI 判斷**只處理兩邊都沒命中的,結果是允許、拒絕、或不確定,後面兩種一樣回到你手上。最後由這個 session 的模式決定它是只**建議**那個鍵,還是直接**幫你按**。 人不在旁邊,責任還是你的。一條在行動網路上核准的指令,跑在那台機器上,那裡存著你的 `.env`、你的金鑰,還有一個不只你一個人在用的 repo。AI 判斷看到的是畫面,它不知道 agent 為什麼走到這一步。所以先選只給建議的模式,把你每天都會核准的指令放進白名單;訊號和注意力都不好的時候,先讀畫面上那條指令再按。黑名單會存在,是因為「全部核准」是一個壞的預設,而距離不會讓它比較安全:指向家目錄的 `rm -rf`,不會因為你人在月台上就比較溫和。 ## claude: command not found,但在 Terminal 裡跑得好好的 同一台機器、同一個帳號,兩種結果:在 Terminal 裡打 `claude` 會跑,透過 SSH 就給你 `claude: command not found`。看起來像遠端那套設定壞了,但這其實是從手機連進去最常撞到的問題。 Claude Code 如果是用 npm 裝的,或是裝在 nvm 釘選的 node 版本底下,它的安裝目錄會寫進一個啟動檔。只有*互動式 login shell* 會讀那個啟動檔,PATH 裡才有那一筆。`ssh host claude` 這種只跑一條指令的連線,啟動的是非互動式 shell,它根本不會讀那個檔案。 最穩的做法是不靠 PATH。在原本跑得動的 Terminal 裡: ``` which claude # /Users/you/.nvm/versions/node/v20.11.0/bin/claude ``` 把那個絕對路徑整串當成 agent 指令,或直接打在 session 裡。nvm 換了 node 版本,這個路徑就會失效。之後要更穩,得看你的 shell 和發行版,那些寫在疑難排解那頁。 ## 跑一整晚可以,重開機不行 機器不睡,session 就能跑一整晚:十一點丟一個重構給它,早上配著咖啡看結果。有三件事會卡住。 **機器要維持清醒。**Mac 閒置進入睡眠時,網路介面跟著休眠,遠端登入開不開都沒用,因為沒有東西在回應。「系統設定 → 電池」(筆電)或「系統設定 → 節能」(長期開機當伺服器用的桌機):把閒置睡眠的時間調長或關掉,有「喚醒以供網路存取」這個選項就打開。 **重開機會把 session 一起帶走。**tmux 不會在重開機之後還原 session,系統更新要重開機時也不會問你。收工之前先 commit,最壞的情況就從損失整個任務變成損失一個小時。 **在等你的 agent 不是當掉的 agent。**先接回去看一眼。一個全新的 shell 提示字元代表 process 已經結束,跑完了或是死了,上面幾行會告訴你是哪一種。另一個 session 如果停在一個問題上,它已經等你一個字等了六小時,也沒花掉什麼。從鎖定螢幕看,這兩種一模一樣,而且只有一種需要你動手。 ## 什麼歸手機,什麼歸書桌 | 在手機上做 | 在書桌前做 | | --- | --- | | 讀完那條指令之後回答權限提示 | 看一段你沒預期會看到的 diff | | 在它給的兩個做法之間選一個 | 判斷一個橫跨好幾個檔案的改動 | | 讀一段短的 diff、commit、push | 讀一段長的 diff,或一個你沒開過的檔案 | | 在第二個 session 裡看測試跑完 | 追那些測試印出來的失敗 | | 睡前把下一個任務丟給它 | 寫一段必須很精確的 prompt | | 連線斷了十次,再接回來 | 修連線本身,或它後面的 PATH 問題 | 三樣東西:一台不會睡的機器、一個有名字的 session、一條只有你進得去的路。少一樣,setup 那頁會先問你三個問題,再照你那台機器給一份清單。 ## AI 自動權限審查:agent 問你時,誰來回答 URL: https://agentmux.saxcave.cc/guide/zh-TW/permission-review/ Language: zh-TW Updated: 2026-11-06 Agent 停下來問「要不要執行」時誰在回答:本機黑名單先擋、白名單放行你信任的,其餘交給 AI 判斷,再依模式給建議或直接按鍵。 AI 自動權限審查替你回答 agent 的權限提示:黑名單先擋下危險指令,白名單放行你已經信任的指令,兩邊都沒命中的才交給 AI 判斷。判斷為允許時,顧問模式給你建議,自動回覆模式幫你按下那個鍵。 agent 跑到一半停下來問「要不要執行這個指令?」,而你剛好不在螢幕前,整個任務就卡在那裡,等這一個按鍵。 AI 自動權限審查就是為這一刻做的。它回答 agent 的提示,一次只按一個鍵,不會替你打指令,也不會去碰畫面上以外的東西。 - **Agent 停下來等你**Agentmux 偵測到權限提示 - **看清楚畫面**找出目前的提示,遮蔽敏感資訊 - **黑名單**命中危險指令*交給你* - **白名單**命中你允許的指令,直接決定按哪個鍵 - **AI 判斷**允許、拒絕,或不確定*拒絕或不確定:交給你* - **送出前再確認一次**畫面變了就不送*交給你* - **給你建議,或幫你按**看你選的模式 ## agent 停下來等你,審查才開始 Agentmux 本來就會辨識 Claude Code、Codex 這類 agent 的狀態。畫面出現「要不要允許」或「選 1 還是 2」這種提示時,它就把那個工作階段標記成等待中,審查從這一刻開始。 同時要符合這幾個條件: - 「設定」裡的 AI 自動權限審查開關是開的。 - 這個工作階段選了「顧問」或「自動回覆」模式,不是「關閉」。 - 你是 Pro 使用者,或還在試用期內。 同一個提示只會判斷一次,不會每隔幾秒重問一遍。你自己在終端機裡按了鍵或打了字,Agentmux 就取消還在進行的判斷,以你的操作為準。 你切到別的分頁之後,它在背景繼續幫你看著那些不在畫面上的工作階段。 ## 它只讀目前畫面上那一個提示 它先從畫面上找出目前那一個提示:要執行的指令、問題本身,還有可以選的選項。已經捲上去的舊內容不算數。 送給 AI 之前,它會先把畫面裡看起來像密碼、API key、token 的內容遮蔽掉。遮蔽失敗時,原始內容不會送出,這一次也不幫你按任何鍵。 ## 黑名單永遠先跑,而且就在手機上 黑名單在你的手機上比對,永遠第一個檢查。命中就不會送給 AI,直接交給你處理。內建規則涵蓋最危險的幾類操作: - 對 `/`、`~` 或家目錄執行 `rm -rf` - `sudo` - `git push --force`(`--force-with-lease` 不算) - `DROP TABLE`、`DROP DATABASE`、`TRUNCATE TABLE` - 把網路下載的內容直接丟給 shell 執行,例如 `curl … | sh` - `mkfs`、`dd if=` 這類直接寫入磁碟的指令 這些內建規則可以編輯或移除,你也可以加上自己的規則。 ## 白名單命中,就不用等 AI 有些指令你每天都在按「允許」,例如跑測試或 build。把它們加進白名單,下次命中時,手機自己就決定要按哪個鍵,不用等 AI,也不花 API 用量。 「自動回覆」模式下,你還可以對單一工作階段開啟「允許此工作階段中的所有命令」。這樣會跳過 AI 判斷,黑名單照樣有效,工作階段結束就自動關掉。 白名單命中了,但畫面上找不到明確又安全的鍵時,一樣交給你。 ## AI 能決定什麼,不能決定什麼? 黑名單和白名單都沒命中的提示,才會送給 AI。它看到的是遮蔽過的畫面,加上你寫的**主要意圖**,也就是你這次要 agent 做什麼,例如「修好登入頁的測試」。 你可以選兩種引擎: - **JEV 決策引擎**(透過 OpenRouter 或 TypeSafe):專為終端機裡的提示設計,回應快。判斷分三級:純讀取的安全操作允許;符合主要意圖的指令允許;其餘不相關、看不懂或風險較高的,全部交給你。看起來可疑或像惡意注入的內容會直接拒絕。 - **OpenAI 相容的 LLM**:OpenAI、OpenRouter,或是你自己架在本機的模型都可以。 AI 的結論只有三種:允許、拒絕、不確定。只有「允許」會繼續往下走,而且它必須明確指出要按畫面上的哪一個選項。「永遠允許」「不要再問」這類永久授權,它不會選。 ## 顧問只給建議,自動回覆幫你按 每個工作階段可以分別選模式: - **顧問**(預設):只給建議。畫面上方會跳出提示,告訴你它建議按什麼,旁邊有「採納」按鈕,按下去才會送出。 - **自動回覆**:判斷為允許時直接幫你按,並跳出提示告訴你它按了什麼。 不管哪一種模式,送出按鍵之前都會再看一下畫面:提示換了、選項順序變了,或是游標不在目標上,就不送。判斷要花幾秒,這幾秒之內畫面可能已經變了,多這一次確認,就是為了不要按到錯的選項。 自動回覆送出一個鍵之後,同一個工作階段會先停 3 秒,才會再送下一個。 ## 哪些情況一定交回給你? - 命中黑名單。 - AI 判斷為拒絕或不確定。 - 看不出這個提示該按哪個鍵。 - 要選的是「永遠允許」這類永久授權。 - 送出前發現畫面已經變了。 - 敏感資訊遮蔽失敗。 交給你的時候,Agentmux 會照平常的方式提醒你,就跟沒開審查時一樣。 ## 要怎麼打開它? 開始之前先準備好其中一種: - JEV 引擎的 API key,透過 OpenRouter 或 TypeSafe 取得。 - 你自己的 OpenAI 相容 LLM 端點,例如 OpenAI 的 API,或架在自己電腦上的模型。 - 到「設定」→「AI 自動權限審查」把開關開啟。第一次會看到資料分享的說明,按下同意之後,會直接帶你進入「進階設定」。 - 在「進階設定」選擇引擎、填入你的 API key,按「測試 AI 端點連線」確認可以用。 - 回到設定頁,在「自動權限審查模式」選預設模式(先選「顧問」就行),再寫下你的主要意圖。 - 連到伺服器、啟動 agent,新開的分頁就會套用預設模式。要調整單一工作階段的模式或主要意圖,點終端機工具列上的自動權限審查按鈕。在那裡也可以開啟「使用我最近的提示」,讓主要意圖跟著你在這個分頁送出的每一條提示更新。 先用顧問模式跑幾天,看看它的建議合不合你的習慣,再考慮對特定工作階段開啟自動回覆。 ## 哪些資料會離開你的裝置? 離開裝置的只有兩樣:遮蔽過的畫面片段,和你的主要意圖。它們送到你自己設定的 AI 服務,用的是你自己的 API key。開啟「使用我最近的提示」之後,你在那個分頁送出的提示會成為主要意圖,也一起送出。Agentmux 不提供也不經手任何 AI 服務。每次判斷的紀錄只存在你的裝置上,可以在稽核紀錄裡查看或清除。模型的推理內容不會儲存。 ## 卡住了怎麼辦 URL: https://agentmux.saxcave.cc/guide/zh-TW/troubleshooting/ Language: zh-TW Updated: 2026-11-06 連不上、金鑰被拒、找不到 tmux、agent 指令不存在:八個卡點,各有原因和解法。 透過 SSH、tmux、Tailscale 做遠端開發,最常卡住的就是下面這八種:`Operation timed out`、`Permission denied (publickey)`、密碼正確卻被拒絕、找不到 tmux、`claude: command not found`、Tailscale 顯示已連線卻連不到、遠端登入開著但 Mac 不接、`sudo: a terminal is required`。每一種都分成症狀、原因,和你可以照著做的解法。 照著前面幾篇設定,十次有八九次一次就通。剩下那一兩次卡住的地方,通常就是這八種。找到你看到的錯誤訊息跳過去看,不用整篇從頭讀。 ## 連線逾時(Operation timed out) ### 症狀 打了 `ssh 使用者@主機` 之後畫面卡住不動,等了半天,才跳出 `Operation timed out` 或 `Connection timed out`。SSH 從頭到尾沒有問過你密碼。 ### 原因 - host 名稱打錯字,例如打成 `my-serverr`。這種情況 SSH 會立刻回報 `Could not resolve hostname`,不會卡到逾時,看到它就先去檢查拼字。會卡到逾時的通常是 IP 打錯:少寫一段、錯一個數字,或是連到一個根本沒人回應的地址。 - 對方的 SSH 不是開在標準的 22 port,你少打了 `-p`。 - 目標機器正在睡眠,或乾脆是關機的。 - 中間有防火牆擋掉連線,例如家用路由器、公司網路、飯店的 Wi-Fi。機器根本沒收到你的請求。 ### 解法 逾時代表你的請求沒有抵達對方,所以要一段一段確認,不要一直猜。先看機器本身有沒有回應: ``` ping 主機位址 ``` `ping` 完全沒回應?先檢查 IP 有沒有打錯,再確認對方機器是不是在睡眠或關機。有些機器會刻意擋 `ping`,所以這裡不通不代表機器真的連不到,直接跳下一步。 接著確認對方那個 SSH port 真的有服務在監聽: ``` nc -vzw 5 主機位址 22 ``` 把 `22` 換成對方實際使用的 port。`-w 5` 讓指令最多等 5 秒,碰到被擋掉的 port 也會在 5 秒後給你結果,不會卡著不動。 結果只有三種。`succeeded` 或 `open`:port 是通的,問題在別的地方。`Connection refused`:機器收得到你的連線,但那個 port 上沒有服務在聽。又逾時:中間有東西把封包擋掉,通常是防火牆,要檢查的是路由器設定或所在網路的限制,不是這台伺服器。 ## Permission denied (publickey) ### 症狀 連線一發出去馬上被拒絕,畫面直接印出 `Permission denied (publickey).`,SSH 連密碼都沒問。 ### 原因 伺服器不接受你這把金鑰。最常見的原因是公鑰根本沒放到伺服器上。 另一個容易被忽略的原因,是 `~/.ssh` 這個資料夾或 `authorized_keys` 的權限太寬。SSH 對這件事非常嚴格:權限不對,它就直接忽略整個檔案,不管裡面的金鑰是不是正確的。 ### 解法 多數情況下是公鑰還沒放上伺服器,最快的做法是用 `ssh-copy-id` 放上去:它會先用密碼登入一次,自動把你本機的公鑰加進伺服器的 `authorized_keys`。 ``` ssh-copy-id 使用者@主機 ``` 這個做法的前提是伺服器的密碼登入還開著;如果密碼登入已經被關掉(見下面 密碼正確卻被拒絕 那節),就需要請能存取這台機器主控台的人,直接把公鑰內容貼進 `~/.ssh/authorized_keys`。 如果公鑰確實在裡面(一行一把、內容完整沒被截斷),卻還是被拒絕,問題多半出在權限。對照下表檢查。最容易漏掉的是最後一行:`~/.ssh` 和 `authorized_keys` 都設對了,但家目錄本身「群組」或「其他人」可寫,`StrictModes` 一樣拒絕: | 路徑 | 需要的權限 | 指令 | | --- | --- | --- | | ~/.ssh | 700 | chmod 700 ~/.ssh | | ~/.ssh/authorized_keys | 600 | chmod 600 ~/.ssh/authorized_keys | | ~(家目錄本身) | 群組、其他人不可寫 | chmod go-w ~ | 改完權限後再試一次連線。如果還是不行,可以在本機加上 `-v` 看詳細過程,確認 SSH 真的有把你預期的那把金鑰送出去: ``` ssh -v 使用者@主機 ``` 輸出會有五、六十行,你只需要其中兩行。`Offering public key: ...` 代表你的金鑰真的送出去了,本機這邊沒問題。緊接著出現 `Authentications that can continue: publickey`,代表伺服器收到了這把金鑰但沒有接受。那就是金鑰放錯地方或權限不對,照上面的步驟繼續查。 ## 密碼正確卻被拒絕 ### 症狀 你很確定密碼打對了(甚至是複製貼上的),但 SSH 一直跳回 `Permission denied, please try again.`,或者根本沒有讓你輸入密碼的畫面就結束了。 ### 原因 這通常跟密碼對不對無關。伺服器的 SSH 設定把密碼登入關掉了(`PasswordAuthentication no`)。這是很常見的安全設定,用意是讓所有人都改用金鑰登入,順帶讓暴力猜密碼失去用處。設定成這樣,密碼就算一百次都打對也不會被接受。 接下來這件事最浪費時間。較新的系統會在 `sshd_config` 最上面放一行 `Include /etc/ssh/sshd_config.d/*.conf`,把那個資料夾裡的設定檔全部讀進來,Ubuntu 22.10 之後、Debian 12,以及幾乎所有雲端主機的預設映像檔都是這樣。而 sshd 一個設定只採用它最先讀到的那個值,後面出現的不會蓋掉前面的。 所以只要 `/etc/ssh/sshd_config.d/` 底下有個 `50-cloud-init.conf` 之類的檔案同樣寫著 `PasswordAuthentication no`,它就比你剛在主檔案改的那一行先生效。你存檔、重啟服務,然後一樣被拒絕。 ### 解法 優先做法:改用金鑰登入(做法見上面 Permission denied (publickey) 那節,把你的公鑰放到伺服器的 `authorized_keys`)。 如果你有這台伺服器的管理權限、也確定想開放密碼登入,才去改設定。先確認有沒有其他檔案會蓋掉你等一下要改的值: ``` sudo grep -r PasswordAuthentication /etc/ssh/sshd_config /etc/ssh/sshd_config.d/ ``` 如果 `/etc/ssh/sshd_config.d/` 底下也有檔案設定了 `PasswordAuthentication`,把那個值一起改掉,或直接註解掉那一行。確認乾淨之後,才編輯 `/etc/ssh/sshd_config`,把 `PasswordAuthentication` 那一行改成 `yes`。 **先不要關掉你現在這個連線視窗。**改壞 sshd 設定可能讓你之後完全連不進這台機器。確認新設定沒問題之前,留著這個視窗當退路。存檔後先驗證語法: ``` sudo sshd -t ``` 沒有印出任何錯誤,才重新啟動服務讓設定生效: ``` sudo systemctl restart ssh ``` 部分發行版的服務名稱是 `sshd` 而不是 `ssh`。上面那行找不到服務的話,改試 `sudo systemctl restart sshd`。接著開一個全新的連線視窗(不要用你留著當退路的那一個),確認真的能用密碼登入,然後才關掉舊視窗。 整套環境都建立在 Tailscale 上,所以有個比「把密碼登入對整個網際網路開放」更安全的做法:只允許 Tailscale 網段的連線用密碼,其餘來源仍然只能用金鑰。在 `sshd_config` 尾端加一段: ``` Match Address 100.64.0.0/10 PasswordAuthentication yes Match All PasswordAuthentication no ``` ## 找不到 tmux ### 症狀 Agentmux 提示找不到 tmux,或者你自己連上去手動打 `tmux` 時看到 `command not found: tmux`。 ### 原因 目標機器上還沒裝 tmux,而 Agentmux 會替你裝。它會先偵測這台機器用的是 apt、yum、dnf、pacman、apk(Alpine)還是 brew,再透過 SSH 跑對應的安裝指令。多數情況你等它跑完就好。 自動安裝失敗,最常見的原因是 `sudo`。安裝指令裡帶了它,但這條通道是非互動式的,`sudo` 沒有終端機可以問你密碼(見下面 sudo: a terminal is required to read the password)。其次是在 Mac 上用 Homebrew 安裝、但連線身分是 root,因為 Homebrew 設計上拒絕以 root 執行。 還有一種少見但容易誤判的情況:tmux 已經裝了,只是裝在 Agentmux 探測 PATH 時沒看到的地方,例如 Linuxbrew 裝在 `~/.linuxbrew`,MacPorts 裝在 `/opt/local/bin`。訊息一樣是「找不到 tmux」,問題卻出在 PATH,不是沒裝。照下面 claude: command not found,但桌面終端機明明可以跑 那節處理。 ### 解法 先看 Agentmux 跳出的錯誤訊息。如果它要你輸入 sudo 密碼、或提到啟用免密碼 sudo,直接把密碼填進去再試一次,通常比自己另外開視窗快。如果這個管道也失敗,或訊息提到終端機、TTY,再照 sudo: a terminal is required to read the password 那節手動處理。處理完通常自動安裝就會成功,不需要再往下做。 只有排除 `sudo` 問題之後還是裝不起來,才手動安裝: ``` # Debian / Ubuntu sudo apt install -y tmux # RHEL / CentOS sudo yum install -y tmux # Fedora sudo dnf install -y tmux # Arch sudo pacman -S tmux # Alpine sudo apk add tmux # Mac(不要用 root 身分執行) brew install tmux ``` ## claude: command not found,但桌面終端機明明可以跑 ### 症狀 你在 Mac 自己開的 Terminal 裡打 `claude`,指令正常執行。透過 Agentmux 連進去、或直接 `ssh 主機 claude`,卻看到 `claude: command not found`(或 `bash: claude: command not found`)。同一台機器、同一個帳號,兩邊結果卻不一樣,看起來就像 Agentmux 壞了。 ### 原因 問題出在 shell 的行為,不是 Agentmux。這是整頁最難自己想通的卡點,所以原理值得多寫幾行。把它讀懂之後,再遇到類似的情況,你自己就能判斷。 你平常在 Mac 上開的 Terminal 是一個「互動式登入 shell」。`claude` 找得到,通常是因為 nvm、npm 或安裝工具在某個設定檔裡加了一行,把它所在的目錄放進 PATH。哪一個設定檔,zsh 和 bash 不一樣,混著講會誤導,所以分開說: **zsh**(Mac 的預設 shell):互動式登入 shell 依序讀 `.zshenv` → `.zprofile` → `.zshrc` → `.zlogin`。而 `ssh 主機 claude` 這種執行單一指令的連線,開的是非互動、非登入的 zsh,只會讀 `.zshenv`,其餘全部跳過,其中就包含大多數人放 PATH 設定的 `.zshrc`。 **bash**:這部分的行為因發行版而異,沒有一套統一的規則。互動式登入 shell 讀的是 `.bash_profile`(或 `.profile`),不是 `.bashrc`。你會以為 bash 也會讀 `.bashrc`,是因為多數發行版預設的 `.bash_profile` 裡都加了一段「如果 `.bashrc` 存在就 source 它」。`ssh 主機 claude` 這種用法,就是 sshd 叫 bash 執行單一指令。這種情況下 bash 會不會讀 `~/.bashrc`,答案要看發行版,差別在一個編譯時期加上去的 `SSH_SOURCE_BASHRC` 特例。Debian、Ubuntu 和它們的衍生版有把這個 patch 補進去,所以非互動的單一指令 shell 也會讀 `~/.bashrc`。RHEL、Fedora、Arch 這類發行版用的是官方原始版本的 bash,沒有這個特例,同樣情境下 `~/.bashrc` 根本不會被讀到。就算在會讀的發行版上,幾乎每個預設的 `.bashrc` 開頭也都放了一段判斷: ``` case $- in *i*) ;; *) return;; esac ``` 意思是「如果現在不是互動模式,就直接結束,不要往下讀」。這段判斷通常寫在檔案最前面,所以 bash 即使真的把 `.bashrc` 打開了,也會在讀到你加的 PATH 那行之前就跳出去,結果跟沒讀到一樣。 不管 zsh 還是 bash,結果都一樣。設定 PATH 的那一行,只在你自己開 Terminal 的時候執行過。SSH 執行單一指令時沒讀到它,PATH 裡就沒有 `claude` 的位置,shell 說找不到是正確的反應。這跟 `claude` 有沒有裝好無關:它一直都在,只是沒人告訴這個 shell 要去哪裡找。 非互動執行時跳過這些設定檔是刻意的設計。不然每跑一段遠端指令,都要先載入一堆給人互動用的別名和提示字元設定,出錯的機會跟著變多。 還有一點。Agentmux 在 Mac 上探測 PATH 用的是 `zsh -lc`,也就是登入但非互動,一樣跳過 `.zshrc`,只讀 `.zshenv` 和 `.zprofile`。所以對 Mac 上的 zsh 使用者來說,把 PATH 設定搬到這兩個檔案,影響的不只是你手動 SSH 找不找得到指令,還包括 Agentmux 自己能不能找到 agent 指令。 ### 解法 最快的做法:找出 `claude` 的絕對路徑,直接用它呼叫,不要依賴 PATH。在平常跑得動 `claude` 的那個 Terminal 視窗執行: ``` which claude ``` 會印出類似 `/Users/你的名字/.nvm/versions/node/v20.11.0/bin/claude` 的一整串路徑。把它填到 Agentmux 要求輸入 agent 指令的欄位,取代原本的 `claude`。 如果你是用 nvm 裝 node,這條路徑裡帶著版本號碼,升級一次就失效,得再跑一次 `which claude` 重新抓路徑。不想每次升級都改,就用下面比較長久的做法。 比較長久的做法,依你的 shell 分開處理,不要混用: **如果是 zsh**(Mac 預設):把設定 PATH 的那幾行從 `.zshrc` 搬到 `~/.zshenv`。那幾行通常來自 nvm、pyenv,或你自己加的 `export PATH=...`。搬到 `~/.zprofile` 也可以,兩個檔案在 SSH 非互動執行和 Agentmux 探測 PATH 時都讀得到。 **如果是 bash**:搬到 `~/.profile` 或 `~/.bash_profile` 不會生效。`ssh 主機 claude` 開的是非登入 shell,這兩個檔案都不會被讀到。 機器是 Debian、Ubuntu 或它們的衍生版的話,把 PATH 那幾行搬進 `~/.bashrc`,放在檔案最前面那段 `case $- in ... esac` 判斷式「之前」,它就會在互動判斷擋下之前先生效。RHEL、Fedora、Arch 這類發行版則不同,`~/.bashrc` 在這個情境下本來就不會被讀到,改它沒有任何效果。 不知道自己屬於哪一種的話,就用上面的絕對路徑。那個做法每個發行版都有效,也是唯一保證跨發行版都能用的解法。 搬完之後,開一個新的 SSH 連線測試,確認真的生效了: ``` ssh 主機 'claude --version' ``` 能印出版本號,就代表 PATH 這次真的在非互動 shell 裡也讀到了。 ## Tailscale 顯示已連線,SSH 仍連不到 ### 症狀 手機上的 Tailscale App 顯示兩台裝置都是綠燈、在線,但用 Agentmux 或直接 `ssh` 連過去,還是失敗或整個逾時。 ### 原因 - 連線用的是區網 IP(像 `192.168.x.x`),不是 Tailscale 給的 MagicDNS 名稱或 `100.x` 開頭的 Tailscale IP。區網 IP 只有在同一個 Wi-Fi 底下連得到。手機一切到行動網路,這組 IP 就完全無效。 - 對方裝置的狀態其實剛斷線或正在睡眠,Tailscale App 上顯示的「在線」還沒更新。 - Tailscale 本身正常,但目標機器上的 SSH 服務根本沒開或沒啟動。Tailscale 只負責把兩台機器的網路打通,不會幫你把 SSH 服務打開。 - 最常見的原因是 node key 過期。目標機器需要重新驗證,App 裡看起來還是綠燈「已連線」,實際上這台機器已經不在網路裡。到 Tailscale 的管理後台查那台機器有沒有被標成需要重新登入,重新驗證一次通常就解決了。 ### 解法 先從目標機器查起,因為 App 上的綠燈可能是舊的狀態。手機上的 Tailscale 沒有指令列,`tailscale status` 只能在目標機器(要被連線的那台 Mac 或 Linux 機器)上執行: ``` tailscale status ``` 在目標機器上確認清單裡手機那個節點顯示在線。手機這邊打開 Tailscale App,確認目標機器的狀態同樣正常:沒有變灰,也沒有需要重新登入之類的警示。 兩邊都正常的話,連線時一律用 MagicDNS 名稱(例如 `mac-mini.tail1234.ts.net`)或 `tailscale status` 列出的 `100.x` IP,不要用區網 IP。這樣不管你在家裡的 Wi-Fi 還是外面用行動網路,位址都有效。 以上都沒問題,再到目標機器檢查 SSH 服務本身有沒有啟動。Linux 用 `sudo systemctl status ssh`;Mac 請見下面 Mac 開了遠端登入卻連不上 那節。 ## Mac 開了遠端登入卻連不上 ### 症狀 已經在系統設定裡打開「遠端登入」,Mac 也確定連著網路,但 SSH 還是連不上;或者連得上卻被拒絕,而你很確定密碼或金鑰都是對的。 ### 原因 - 「遠端登入」預設只允許「特定使用者」清單裡的帳號登入。你用來連線的那個帳號可能根本不在清單上。 - Mac 進入睡眠之後網路介面跟著休眠,SSH 自然連不到。這跟遠端登入有沒有打開無關,是機器當下對網路請求完全沒反應。 ### 解法 打開「系統設定 → 一般 → 共享」。那是 macOS Ventura 之後的路徑,Ventura 之前是「系統偏好設定 → 共享」,沒有「一般」這一層。找到「遠端登入」,確認開關打開,再點進去看「允許存取」的使用者清單裡有沒有你要連線的那個帳號,或直接選「所有使用者」。 接著打開「系統設定 → 電池」(筆電)或「系統設定 → 節能」(Mac mini、Mac Studio、iMac 這類長期開機當伺服器用的桌機,上面範例裡的 `mac-mini.tail1234.ts.net` 就是這種)。把「電腦閒置時進入睡眠」的時間調長或關掉。如果面板上有「喚醒以供網路存取」這個選項,一併打開,Mac 就算睡著,收到連線請求時也能醒過來回應 SSH。 ## sudo: a terminal is required to read the password ### 症狀 透過 Agentmux 或 SSH 執行一段包含 `sudo` 的指令時,指令直接失敗,印出 `sudo: a terminal is required to read the password`,完全沒有跑起來的跡象。 ### 原因 `sudo` 正常會在終端機上跳出「輸入密碼:」,等你打完才往下跑。但如果指令是透過非互動的管道執行,例如 SSH 遠端執行單一指令,或是 Agentmux 在背後幫你跑的安裝流程,那裡沒有一個真正的終端機可以顯示提示,也沒有地方讓你打字。`sudo` 發現沒有 TTY 可用就直接拒絕,不會傻等一個永遠不會來的輸入。 這裡有個常見的誤會:以為剛打過一次 sudo 密碼,接下來一段時間就不用再打。現代的 sudo 確實會記錄「已經驗證過」,但那筆記錄綁的是當下那個 TTY(`tty_tickets`),跟你的帳號、跟這台機器都沒有關係。 你在互動式 SSH 視窗裡打過密碼,Agentmux 之後自動跑指令時,開的是另一條沒有 TTY 的全新連線。對 sudo 來說那是另一筆記錄,所以它會再問一次。這裡沒有快取過期的問題:兩邊查的從來就不是同一筆記錄。 ### 解法 非互動的管道沒有地方讓 sudo 問密碼,所以解法有兩條:讓 Agentmux 幫你填密碼,或自己在真正的終端機裡跑一次。先看 Agentmux 跳出的提示:自動安裝失敗時,它通常會直接請你輸入 sudo 密碼、或改成啟用免密碼 sudo,再重試一次。看到那個欄位就把密碼填進去,往往比自己另外處理快。 這個管道不可用的話,就自己手動 SSH 登入那台機器,開一個正常的互動式 session,在裡面執行一次同樣的指令(例如 `sudo apt install -y tmux`)並輸入密碼,把事情做完。 它會成功,理由卻跟多數人想的不一樣。這沒有幫 Agentmux 之後的自動流程解鎖什麼,兩邊查的 TTY 記錄本來就不是同一筆。真正的原因是該裝的東西已經被你裝好了:下一次非互動指令檢查時發現東西已經在,不需要安裝,卡點就消失了。 如果某個特定指令長期都要透過非互動管道免密碼執行,可以用 `sudo visudo` 幫它加一條 `NOPASSWD` 規則,只針對那一條指令放行: ``` 你的帳號 ALL=(ALL) NOPASSWD: /usr/bin/apt ``` 這個取捨比看起來嚴重。把 `NOPASSWD` 開給 apt 這類套件管理器,幾乎等於交出完整的 root:你可以叫 apt 裝任何套件,而 `APT::Update::Pre-Invoke` 這類 hook 還能以 root 身分在安裝過程中執行任何指令。實際上那就是免密碼的 root,只是換一種寫法。 清楚知道這個代價、也刻意接受,才這樣設定。否則寧可回到上面「先手動裝一次」的做法。也絕對不要直接放行 `NOPASSWD: ALL`,那等於整台機器的 `sudo` 都不再需要密碼。