> ## 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.

# Box rules

> Plan limits, credit, snapshots, sleep and wake, and finding boxes

The exact rules for boxes, for looking things up. What a single command
does is in the [CLI reference](https://tesser.sh/docs/cli-reference.md), and timers and box sizes are
in [Configuration](https://tesser.sh/docs/config.md).

The [Boxes](https://tesser.sh/docs/boxes.md) page explains the ideas.

## Plan limits

| | Hobby | Pro | Team |
| - | - | - | - |
| Boxes per org | 3 | 10 | no cap |
| `--size large` and `xlarge` | no | yes | yes |
| `tesser sleep-after` | 5 minutes to 2 hours | 5 minutes to 24 hours | 5 minutes to 24 hours |
| `tesser keep` | no | yes | yes |
| Usage past what the plan includes | stops | billed, up to the spend limit | billed, up to the spend limit |
| Boxes in your own AWS account | no | no | yes |

* The box cap counts boxes, awake or asleep. At the cap, `make`, `exec`, and
  `dev` fail with `box_limit`. `tesser rm` an idle box or upgrade.
* An admin sets the spend limit with `tesser spend-limit` or on the
  dashboard's Billing tab. At the limit, the org's awake boxes sleep, and
  launches and wakes fail with `credit_exhausted` until the limit is raised
  or the month resets.
* Pool boxes belong to no repo. Any `make`, `exec`, or `dev` in the org
  claims one of its size.

### Credit

* Usage is metered per second. The [pricing page](https://tesser.sh/pricing)
  has the rates. `tesser usage` and the dashboard show what an org has used
  and how much credit is left.
* The first org you create, on the page `tesser login` opens or on the
  dashboard, starts on Hobby with \$5 of credit. An org you create while you are already an admin of
  another starts with none.
* With no credit left, launches and wakes fail with `credit_exhausted` until
  an admin upgrades on the dashboard's Billing tab. An org on a trial instead
  sleeps until more credit is added or the trial ends.
* When the credit or spend limit left covers less than two hours of the
  org's awake and asleep boxes, `make`, `dev`, `status`, and `sync` print one
  `tesser: warning:` line to stderr: roughly when the boxes will sleep, and
  how to add credit or raise the limit. Out of credit, the same line says so.
  Nothing goes to stdout, so `--json` output stays valid.

## Snapshots

### What a box gets from one

* A snapshot is a disk image of a repo taken after every service's `setup`
  ran. It holds `/home/ubuntu`, `/var/lib/docker`, and `/var/lib/containerd`.
* A repo's current snapshot is the newest ready one of a commit on the
  default branch. Every new box in the repo starts from it.
* A claimed pool box mounts the snapshot at claim time and starts while the
  rest fills in. If the mount fails, the box starts without it.
* The box reads the image as it needs it while the rest streams in, so it
  does not wait for the whole disk.
* The first sync sends only what changed since the snapshot's commit, and
  package stores and `node_modules` are already in place, so the first
  install is incremental. Without a snapshot, the first `setup` installs from
  scratch. Boxes never copy installs from each other.
* Anything `setup` installed outside `/home/ubuntu` and Docker's data is not
  on the new box, except the manifest's `apt` packages, which the box
  reinstalls on every start.
* If a box cannot read its snapshot, it starts on an empty home directory.
* `tesser dev --snapshot <snapshot_id>` starts a new box from a specific
  snapshot of the repo. `tesser dev --no-snapshot` starts one without any.

### Seeing which snapshot a box has

* `tesser ls --json` shows the snapshot each box started from.
* `tesser status <box_id>` prints it on a `snapshot` line as
  `<snapshot_id>@<sha> (<age> old)`, with the first 12 characters of the
  commit, or `-` for a box without one. A claimed pool box shows `-` until
  its snapshot is mounted.

### Building one

`tesser snapshot create` builds a snapshot of HEAD, or of `--sha <commit>`:

1. A builder box launches at the largest size any of the repo's services
   uses, starting from the repo's current snapshot when there is one.
2. It syncs the commit and runs each service's `setup` with the values from
   `tesser env set`.
3. It deletes `~/.tesser`, `~/.ssh/authorized_keys`, `~/.ssh/known_hosts`,
   `~/.bash_history`, `~/.gitconfig`, `~/.git-credentials`, `~/.netrc`,
   `~/.config/tesser`, `~/.config/gh`, `~/.aws`, `~/.claude`,
   `~/.claude.json`, and `~/.codex`, so no one's credentials reach other
   people's boxes.
4. It captures the disk.

* A repo builds one snapshot at a time, and a build that runs past its
  [time limit](https://tesser.sh/docs/config.md#timers) fails.
* The builder needs room under the org's box cap.
* `--wait` streams the setup log from the builder over SSH until the
  snapshot is ready or fails. The log stays on the builder, so `--wait` is
  the only way to read it. Without `--wait`, `tesser snapshot show <snapshot_id>` shows the build's state.

### Automatic builds from `tesser dev`

When `tesser dev` runs for a service without `--snapshot` or
`--no-snapshot`, it checks the repo's snapshots once it has a box, against the
default branch's tip as your clone last fetched it (`origin/HEAD`, else
`origin/main`, else `origin/master`).

* If that commit needs a snapshot, it starts `tesser snapshot create` in the
  background and prints
  `tesser: building a snapshot of <repo> at <sha> in the background` on
  stderr, with the path of its log (`snapshot-build.log` in the
  [state directory](https://tesser.sh/docs/config.md#files-on-the-laptop)).
* `tesser dev` does not wait for the build, and Ctrl-C does not stop it.
* It builds nothing when:
  * no service has a `setup` at that commit
  * a snapshot of the repo is already building
  * the repo has a current snapshot that is not stale
  * the last build at that commit failed (it tries again once the tip moves)
  * the newest ready snapshot of the default branch is at a commit the tip
    does not contain
* A background build leaves room for one more box under the box cap. It
  stops quietly when the org has no room, is out of credit, has reached a
  plan limit, or keeps no snapshots.

When `tesser dev` waits for the server and `setup` took 3 minutes or more,
it prints a hint after the box id:

* If the service has no shared instance, it names the
  `tesser make <service> --ensure-running <sha>` command that would share one
  box's setup with everyone.
* If the default branch's tip needs a snapshot, it names the
  `tesser snapshot create --sha <sha>` command that builds one, or says one
  is already building.

### Listing, staleness, and removal

* Inside a repo, `tesser snapshot ls` lists that repo's snapshots and prints
  `this repo (<repo>): none` when it has none. Outside a repo, it lists the
  org's. `--all` lists every repo's, and `--removed` includes removed and
  purged ones.
* A snapshot of a commit not on the default branch is marked `branch`. New
  boxes start from it only with `--snapshot`.
* `tesser env set` marks the repo's snapshots `stale`, because their setup
  ran with the old values. A stale snapshot is still used until a new one is
  built.
* Tesser keeps the newest ready snapshot of the default branch and the newest
  of other branches, and deletes older ones once no box is reading from
  them. `tesser snapshot rm` removes one sooner, and boxes made from it keep
  running.
* An org that [runs boxes in its own AWS account](https://tesser.sh/docs/self-hosting-reference.md#snapshots)
  keeps snapshots in its own S3 bucket and has none until it names one.

## Sleep and wake

### What counts as idle

A box is in use while any of these is true:

* a visible browser tab is showing it
* something other than a browser sends it requests (curl, a script, a test
  run)
* a CLI command targets it
* an SSH session is open where someone is typing, or something is running or
  printing

A background tab, or an open shell with nothing running, does not count.

* Traffic between boxes does not count on its own, but a box in use keeps
  the boxes it depends on awake, so a server and its database idle together.
* A kept box keeps the boxes it depends on awake while its server runs. Once
  its server stops, they go back to their own idle clock.
* When an instance box starts or wakes, it wakes the boxes it depends on
  that you own or that are the org's shared instances. A dependency wired to
  a teammate's box is never woken this way.

### What survives sleep

* A sleeping box keeps its disk and its id. Any command that targets it wakes
  it first, and opening it on localhost shows a Wake button.
* Sleep is hibernation: memory is written to disk, and on wake processes
  carry on where they stopped. A dev server keeps its state and does not run
  `setup` again.
* After a wake, boxd waits for the health check (see [Timers](https://tesser.sh/docs/config.md#timers)).
  A server that does not answer in time is started again from `setup`.
* A wake that updates boxd to a newer version also starts the dev server
  again, even after a good hibernation.
* A dev server that exited before the box went to sleep stays exited after
  the wake.
* `instance.startedAt` in `tesser status <box_id> --json` is when the current
  dev process started, so you can tell whether a wake restarted it.
* Connections between boxes close on wake, so your app has to reconnect to
  its dependencies.
* These boxes get a plain power-off instead, and their dev server starts
  again on wake:
  * a box still streaming in its snapshot
  * a box put to sleep in its first minute
  * a box whose image or AWS settings do not support hibernation

## Finding and inspecting boxes

### `tesser ls`

* Piped, one line per box: kind, service, size, power state, whether it is
  online, when it was made, its IP address, and the local worktree it was
  made from.
* In a terminal, a live table with service, worktree, owner, size, state,
  created, idle, and box columns. Press a column's number or click its header
  to sort, press `/` to fuzzy search, and click a box twice to use it on
  localhost.
* A worktree shows as its folder name, followed by its repository in
  parentheses when the two differ, such as `src (zulip)`. The repository name
  is the last part of the worktree's `origin` URL, or the repository's folder
  when there is no `origin`.
* `tesser status <box_id>` adds the instance id, IP addresses, and when it
  was last active.

### `tesser ssh` and `tesser exec`

* When a command after `--` exits 127 and its command word still has quotes or
  spaces in it, both print one extra line on stderr explaining why. The exit
  code is passed through unchanged.
* The box records each command's exit code in a file under
  `/tmp/tesser-exec/`. The file is empty while the command runs.
* When ssh exits 255 and you did not interrupt it, the CLI reconnects once,
  reads that file, and prints one line on stderr:

  | What it finds | What it says | Exit code |
  | - | - | - |
  | It cannot reconnect | The `tesser ssh` command that reads the file later | 255 |
  | No file | The command never started or the box restarted | 255 |
  | An empty file | The command is still running and its output is lost | 255 |
  | 129 or 141 | The command died with the connection; rerun it | 129 or 141 |
  | Any other code | The command finished on the box | That code |

  When the command itself exited 255, nothing is printed.

### `tesser util`

* The box samples CPU and memory every second and reports a summary each
  minute.
* Each row is one step: mean, 95th percentile, and peak CPU as a percentage
  of all the box's vCPUs, plus mean, peak, and total memory in GiB.
* `--since` takes values like `30m`, `12h`, or `90d`, and defaults to an
  hour. Longer windows use wider steps so the output stays at 360 rows or
  fewer.
* History is kept 90 days and is gone once the box is removed.
* `--json` also includes CPU and memory pressure.


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