> ## Documentation Index
> Fetch the complete documentation index at: https://tesser.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuration

> Environment variables, the files Tesserc writes, the ports it uses, and the timers

There is no configuration file to edit. `tesser login` writes the one
credential the CLI needs, and everything else is controlled by command-line
flags or environment variables.

## Environment variables

| Variable | What it does |
| - | - |
| `TESSER_TOKEN` | Use this API token instead of the saved credential. Intended for CI; use it together with `TESSER_ORG`. |
| `TESSER_ORG` | Run in this org instead of the saved default. |
| `TESSER_CONFIG_DIR` | The directory that holds credentials, the default org, and this laptop's SSH key. Defaults to `~/.config/tesser`, or `$XDG_CONFIG_HOME/tesser` if set. |
| `TESSER_STATE_DIR` | The directory that holds per-box known\_hosts files and daemon state. Defaults to `~/.local/state/tesser`, or `$XDG_STATE_HOME/tesser` if set. |
| `TESSER_DAEMON_PORT` | The port the daemon takes when `dev`, `use`, or `review` start it in the background. Defaults to 3000; `tesser daemon install --port` is the equivalent for the login service. |
| `TESSER_CONTROL_URL` | A different control plane. The production URL is built into the release binary, so this is not normally needed. |
| `TESSER_WEB_URL` | Where `tesser login` sends the browser. Defaults to `https://tesser.sh`. |
| `TESSER_INSTALL_URL` | Where `tesser update` looks for releases. Defaults to `https://install.tesser.sh`. |
| `TESSER_RSYNC` | Override the rsync executable. Releases carry GNU rsync inside the CLI and extract it under the state directory on first use. Source checkouts search `rsync`, then the Homebrew paths, and reject openrsync. |
| `TESSER_TELEMETRY` | Set to `0` to send no usage events. `DO_NOT_TRACK=1` does the same. Release builds send usage events tied to your tesser membership: one per command (the command, the names of the options given but never their values, outcome, error message with your home directory cut out, duration, and whether an agent ran it), one per box reuse or creation, one per switch, wake, or review link opened from `tesser use`, `tesser top`, the switcher, or the status page, and one each time the switcher panel is opened, hidden, or shown. Box ids go as they are; service names and worktree paths only as one-way hashes. Dev builds send nothing. |

## Files on the laptop

| Path | Contents |
| - | - |
| `~/.tesser/bin/tesser` | The binary. |
| `~/.config/tesser/credentials.json` | One API token per control plane and org, with mode 0600. On macOS the tokens themselves are in the login keychain under `sh.tesser.cli`. |
| `~/.config/tesser/config.json` | The default org. |
| `~/.config/tesser/login.json` | A sign-in link that `tesser login` printed and that has not been approved yet, with mode 0600. The CLI deletes it when the login finishes or the link expires. |
| `~/.config/tesser/device-id` | A random id that stands in for you in usage events until you log in. |
| `~/.config/tesser/id_ed25519` | This laptop's SSH key pair, used for every box. The private key never leaves this machine. |
| `~/.local/state/tesser/boxes/<box_id>/` | That box's known host entry (`known_hosts`) and the git refs the laptop last delivered to it (`git.json`). |
| `~/.local/state/tesser/` | Which box belongs to which worktree, the panel's boxes and focus order, the daemon's port and pid, and its log (`daemon.log`). |
| `~/Library/LaunchAgents/sh.tesser.daemon.plist` | The login service on macOS, if you ran `tesser daemon install`. |
| `~/.config/systemd/user/tesser-daemon.service` | The login service on Linux, if you ran `tesser daemon install`. |
| `~/.claude/skills/tesser-cli/SKILL.md` | The agent skill, if you installed it. |

Nothing is installed system-wide, and nothing requires root.

## Ports on the laptop

| Port | Used for |
| - | - |
| 3000 | The daemon's own port: its panel and status page. `TESSER_DAEMON_PORT` or `tesser daemon install --port` moves it. A box that declares 3000 is served there like on any other port. |
| 42000 to 42999 | SSH tunnels to boxes, one per box, chosen automatically. |
| declared ports | Every port a service manifest declares is bound once on the laptop and shared by all boxes, told apart by host name; bare `localhost:<port>` is the focused box that has it. |
| dependency ports | Every `[deps]` port of your boxes, so browser code and laptop tooling reach what the focused box reaches. |

The daemon answers requests from your own machine and from Tailscale peers.
It refuses the rest of the LAN unless it is started with `--allow-lan`.

## Timers

| Event | When |
| - | - |
| Workbench goes to sleep | after 10 minutes idle |
| Instance box goes to sleep | after 2 hours idle, or your `tesser sleep-after` |
| Sleeping box is removed | after 16 hours asleep |
| Unclaimed pool box is removed | 1 hour after the last make it could serve, 6 hours at most |
| Health check gives up | after 90 seconds by default |
| Browser sign-in expires | after 10 minutes |

## The box

Each box is a standard size (2 vCPU, 8 GB of memory) unless `--size` or the manifest picks small (1 vCPU, 4 GB), large (4 vCPU, 16 GB), or xlarge (8 vCPU, 32 GB), with a 40 GB gp3 disk at the gp3 default throughput of 125 MiB/s,
running Ubuntu 24.04 on its LTS kernel (6.8) with Node (22 and 24, via fnm),
Bun, rsync, and Docker (with compose and buildx) installed. Your worktree is at `~/workspace` and the dev server's output is
written to `~/.tesser/dev.log`. Commands run as the `ubuntu` user, which has
passwordless sudo and is in the docker group.

The `setup` and `dev` commands, snapshot builds, `tesser exec`, and a command
line given to `tesser ssh` run on the Node version your repo pins. The box
looks for `.nvmrc`, `.node-version`, or `engines.node` in `package.json`,
starting in the command's working directory and walking up, and the nearest
pin wins. A service's commands start in its directory, and `tesser exec`
without `--in` starts at the repo root. When the pinned version is not
installed yet, the box installs it once and prints
`tesser: installing the node that <dir> pins`; the version stays installed while the box sleeps. When
there is no pin, or the pin cannot be installed, the command runs on the
box's default Node and does not fail. After a failed install the box waits an
hour before it tries that download again. The dev log opens with a
`tesser: node <version>` line naming the version it used. The interactive
shell that `tesser ssh <box_id>` opens does not use this lookup.

The run command sees these variables from Tesserc:

| Variable | Value |
| - | - |
| `TESSER_BOX_ID` | The box's own id. |
| `TESSER_BOX_HOST` | `box-<id>.localhost`, the name the box and its peers use to reach it. |
| `TESSER_BOX_URL` | `http://box-<id>.localhost:<port>` for the first port the manifest declares. |
| `TESSER_SLOT` | Which boxes share backing state (queue names, a redis db). `pinned` on every shared instance, whatever sha it serves; `<worktree folder>-<owner>` on a dev box, so a worktree's boxes share one and a branch switch moves nothing. The value is always lowercase letters, digits, `_` and `-`, at most 63 characters. Key shared-infra names on it instead of the checkout's branch or sha. |

`TESSER_BOX_URL` sits under anything you set with `tesser env set`; the other three are fixed per run.

A command run with `tesser exec`, or a command line given to `tesser ssh`, sees the same variables, with two differences. `TESSER_BOX_URL` is set only on a service box that one of this laptop's worktrees started. `TESSER_SLOT` is set on a shared instance and on a dev box that one of this laptop's worktrees started, and left out otherwise. The interactive shell that `tesser ssh <box_id>` opens gets none of them.

A service's `setup` also runs on the snapshot builder, and the repo's new boxes start from the disk it leaves behind (unless you pass `--no-snapshot`). On the builder, `setup` sees none of these variables. Anything specific to one box, such as its address, its id, or a per-box secret, belongs in `dev` and never in `setup`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.