Skip to main content
The exact rules for localhost routing, for looking things up. What a single command does is in the CLI reference, and timers and box sizes are in Configuration. The See any branch at localhost page explains the ideas.

localhost routing

The daemon

  • tesser dev, use, and review start tesserd in the background when it is not running. tesser daemon install makes it a login service (see tesser daemon).
  • It listens on port 3000 for its own panel and status page, and on every port any of your boxes declares or depends on. A port means that port everywhere: nothing is redirected to another port of another box.
  • It answers only requests addressed to a name that reaches this machine: localhost, any *.localhost name, an IP address, the machine’s hostname or <hostname>.local, and its Tailscale name when Tailscale is running. Any other Host gets a 403 that says to open localhost:<port> instead. A proxy of your own in front of tesserd must send Host: localhost.
  • It answers your own machine and Tailscale peers, and refuses the rest of the LAN unless started with --allow-lan.
  • Your app is shown a localhost address. A request to box-<id>.localhost:<port>, the Tailscale name, the hostname or a LAN IP arrives with Host: localhost:<port>, and acme.box-<id>.localhost with Host: acme.localhost:<port>. A browser’s Origin on any of these is rewritten the same way. A request to 127.0.0.1 keeps its own Host and Origin, except a WebSocket, which gets localhost:<port>.
  • A write (any method but GET and HEAD) with no Origin and no Sec-Fetch-Site, such as curl, sent to a localhost name or a loopback IP, arrives with Origin set to its Host. An Origin from another site, or null, passes through as sent.

Box addresses

  • A box id such as box_a1b2c3 appears in its address as box-a1b2c3, because a Content-Security-Policy does not accept a hostname with an underscore. box_a1b2c3.localhost still reaches the same box.
  • Any tesser command that takes a box id also takes the box’s address, such as box-a1b2c3.localhost:5173 or http://box-a1b2c3.localhost:5173/.
  • Every port a box declares is served, such as box-<id>.localhost:9229 for a debugger. Opening a page on a port the box does not serve, such as 3000, sends the browser to the same path on the box’s own port. Any other request on that port answers 404.
  • The laptop serves the boxes it started, boxes opened from a share link, and the org’s shared instances. A shared instance needs no share link: the first request to its address adds it to the laptop, and opening it in a browser wakes it. tesser service ls shows each shared instance box.
  • A box the laptop cannot add, such as a teammate’s box nobody shared with you, answers 404 NO_TARGET with the reason, such as that the box does not exist or that you are not allowed to run on it. A browser shows the reason as the page heading. When the daemon cannot reach Tesser to ask, the request answers 503 and can be retried.
  • A request from another site’s page, such as an image on an unrelated site, never adds a box. Open the box’s address from the address bar first.

Dependency ports on a box address

  • If a frontend’s manifest says 5000 = "api", box-<id>.localhost:5000 is the api that frontend talks to: its wire, the org’s shared instance, or your box of api from the same worktree, whichever its routing table says now.
  • When that backend is one of your boxes, the request rides the tunnel the laptop already holds to it. Otherwise the daemon opens one through the frontend box’s loopback port.
  • A dependency with nothing behind it answers 503 and names the tesser wire command that fixes it.

A box page calling bare localhost

  • A page on a box’s address stays with that box when it calls bare localhost. If box-<id>.localhost:3000 fetches http://localhost:8080, loads a script or image from it, or opens a websocket to it, the request goes where box-<id>.localhost:8080 would. So a URL your app has baked in stays in that box’s worktree.
  • tesserd recognizes these requests by their Origin header, or Referer when there is no Origin.
  • A port outside that box’s declared and dependency ports goes to the focused box.
  • Page loads on bare localhost are never routed this way. Moving the focus does not reload these tabs or close their sockets.

From a box

  • Code on any of your boxes can open http://box-<id>.localhost:<port> for any instance box in the org and reach that box’s declared port, wired or not. This includes the org’s shared instances, so a shared box can call back into a dev box that handed it its address, such as for webhooks.
  • Workbenches declare no ports, so they have no address, and bare localhost:<port> on a workbench is the workbench itself, which runs no server. A box holds addresses for at most 254 others.
  • A browser or curl on the box, which dials loopback with the name in Host, is routed by name on a port some other box declares.
  • Nothing is bound on the box for this. Anything listening on the box wins its port, whether it started before or after: its own ports, its dependency ports, a database a test started. So from a browser on a box, a peer’s copy of a port this box listens on reaches this box.
  • A port another box declares accepts a connection on this box’s loopback even when nothing local listens, so a raw TCP probe (nc -z, wait-on tcp:) cannot tell the port is free. Probe over HTTP. A port the target box does not declare is refused immediately.
  • The run command gets the box’s own address as TESSER_BOX_URL and its id as TESSER_BOX_ID.
  • <service>.localhost:<port> names whichever box is the org’s shared instance of that service now, so it survives a promote: the name moves to the new box and the old one drains. In-flight connections stay; a new connection after the promote lands on the new box. Use it when the other side should be the shared instance rather than one box, such as a webhook subscription that should survive deploys.

Focus

  • Focusing a box focuses its whole dependency graph. Each box brings its declared ports and dependency ports. Shared dependencies are included even without a local target or SSH key for them: the daemon tunnels through one of your registered boxes.
  • The last explicit focus wins for every port in its subtree. Unrelated ports stay where they were. Removing a focus reveals the one underneath.
  • A missing binding fails with an explanation. It never falls back to an older focus.
  • Shared nodes and cycles are visited once per focused root.
  • Focus changes only the laptop’s routes. Dashboard A’s server process still calls the backend wired to A. To browser-test a backend-only branch B, run a dashboard B and wire it to backend B (see Wiring a dependency).
  • A new tesser dev joins the focus at the bottom, taking unclaimed ports. Re-running an existing box keeps its priority. Starting a service does not rewire its dependencies or launch other services.
For a dashboard wired to a server, where the server uses hp and a worker:

tesser use

  • The focus is saved even when the daemon is unavailable. The command then fails and says routing was not verified.
  • It also fails when the focus is applied but its routes are unavailable. A ready route means the local tunnel accepts connections and the last known box status allows serving. It is not an application health check.
  • tesser use --json prints the same graph and status the panel reads.

TCP

HTTP on a port is routed by name. Anything else is passed through as bytes, so a database dependency is reachable from laptop tools on bare localhost:5432, the same as from the box.

Cookies and sockets

  • localhost and box-<id>.localhost are different sites to the browser, so a Lax or default cookie is not sent between them. A box’s address has its own cookies, so a tab there keeps a sign-in per box.
  • A SameSite=None cookie set on localhost is sent with a box page’s calls to bare localhost, and those calls reach the page’s box, not the focused one.
  • When the focus moves, the proxy closes the old box’s connections on the ports that moved, so a hot-reload socket stops feeding the page from the old branch. Pages on ports that did not move are not touched.
  • A frontend that calls its API at a hardcoded http://localhost:<port> and signs in with cookies should call its box’s address instead and set box_address = true, or serve the API through the dev server’s proxy, so the page only talks to its own origin.

The panel

  • ⌘⇧U opens the panel. It lists the branches serving this port, each followed by its repository when the repository’s name differs from the branch’s. Hovering shows which branch you are on.
  • “Hide until tesserd restarts” takes the square off every page. ⌘⇧U still opens the panel, and expired AWS credentials still show.
  • Choosing a branch focuses the service serving this page and its whole dependency graph. Choosing the current branch again restores its graph after overrides.
  • Ports that fail to connect are listed under the branches.
  • A tab keeps the kind of address it was opened on. On a box address, choosing a branch moves the tab to that branch’s box address on the same path, and each box keeps its own cookies. On bare localhost, the address stays and the page reloads onto the chosen branch.
  • A service with box_address = true never stays on bare localhost: a page opened there moves to the box that port shows, on the same path. Only page loads move.