Watching a terminal you do not host
Running Claude Code on a cloud machine takes a container. Watching it from a browser takes a link the sandbox dials out on, backpressure measured on the socket the browser holds rather than the one the sandbox holds, and a close frame, because a WebSocket ping is answered by the network stack and cannot tell you the reader left.
Moving an agent to a cloud machine is the easy half, and it is the half everybody writes about. A container, an image, a CLI inside it. The half that decides whether the product is usable is the connection between that machine and the browser watching it, and it fails in two ways that no test notices and no user reports accurately.
The lid stops being the runtime
The reason to move an agent off your laptop is not that laptops are slow. It is that the run stops belonging to a device. The agent’s process and its checkout live on the machine we run; your browser sends keystrokes and draws what comes back. Closing the lid ends the watching and not the work, and a long build heats up a machine nobody is sitting at.
That sentence is easy to write and it puts a connection in the middle of the product. Every keystroke and every byte of output now crosses a network that is allowed to be slow, to stall, and to disappear without telling anybody.
The sandbox dials out
The machine that runs agents has no public entry point. It opens one connection to the control plane and keeps it, and every browser attached to that machine is multiplexed over that single link. This is the pattern a self-hosted CI runner uses, and it is chosen for the same reasons: nothing has to be exposed, nothing has to be forwarded, and a sandbox behind any amount of NAT works without configuration.
It also means the interesting failures are not about reachability. They are about what happens on a link that is up and behaving badly.
The first silent failure: measuring the wrong socket
An agent that compiles something noisy can produce output faster than a browser can render it. Somebody has to notice and slow the producer down, and the obvious implementation is for the sandbox to watch its own socket: if the buffer grows, stop writing.
That measures the wrong thing. The socket the sandbox holds is the link to the control plane, which is a fast machine on a good network and will happily accept everything it is given. The socket that is actually backing up is the one the browser holds, and it is at the other end, terminated by a different process.
So the detection has to happen where the slow reader is, and the pressure has to travel back the other way. The control plane watches the browser socket it terminates, and when that browser stops keeping up it stops reading its own link to the sandbox. TCP does the rest: the receive window closes, and the congestion surfaces on the sandbox as its own buffer growing, which is the one place a decision to stop writing can be made.
The price is worth naming, because it is the kind of thing that is discovered later and painfully. Holding reads on a shared link is head of line blocking for every other browser on it. A congested link says “this link is congested”, never “this one viewer is slow”, so a hold is only allowed to last milliseconds, and the per viewer limit stays the control plane’s job. Getting that wrong turns one person’s slow laptop into everybody’s stutter.
The second silent failure: a ping that always answers
The natural way to ask whether the other side is still there is a WebSocket ping. It is also useless for this question, and the reason is worth internalising: a ping is answered by the peer’s network stack, not by the peer’s application. A tab that has been closed, a laptop that has suspended, a page that has crashed: for a while, all of them answer.
The half that terminates the browser socket is the only half that can know the browser is gone. So it says so, explicitly, on the link, and the sandbox learns it from that message and from nothing else. When the link itself dies, every connection still open on it is closed locally, once each, so that a terminal never streams output into an address that no longer points at anybody.
Both rules exist because the symptom of getting them wrong is not an error. It is a slow leak of memory on a machine nobody is looking at, and a session that looks alive in a dashboard while the person it belongs to went to lunch an hour ago.
Why this is not incidental
Anyone can put a CLI in a container. What decides whether people leave agents running is whether the connection between the machine and the person is honest about its own state: it knows when it is congested, it knows when its reader is gone, and it says so instead of guessing.
The reservation board that keeps several agents out of each other’s way is a different problem and has its own piece. The split that keeps secrets off the machine running model written code is a third. This one is the wire between them, and it is the part a demo never shows.
Straight answers
Can Claude Code keep running after I close my laptop?
Yes, when the agent’s process and its checkout are on a cloud machine rather than on the laptop. The browser only sends keystrokes and draws what comes back, so closing the lid ends the viewing and not the run.
Does a cloud agent need an open port on my network?
No. The sandbox dials out to the control plane and keeps one connection open, which is the self-hosted-runner pattern: it needs no public entry point of its own and traverses NAT without configuration.
What happens to a run when the browser watching it dies?
The run continues. The half that terminates the browser socket is the only half that can know the browser is gone, so it says so explicitly on the link; a WebSocket ping is answered by the peer’s network stack and would keep answering for a tab nobody has open.