# Terminal Resume Protocol (OSC 88)

> A proposal for a program to tell its terminal how to relaunch it when the terminal itself is restarted.

- **Sequence:** `OSC 88 ; op ; key=value … ST`
- **Specification:** [Terminal Resume Protocol (TRP), OSC 88 Specification, v1](https://github.com/Otty-sh/osc-88/blob/aabe19cd7a581d4b9c2b7ae3454ca7d294f64d2c/SPEC.md)

The *Terminal Resume Protocol* lets a long-running program, an editor, a
multiplexer, an SSH session, tell its terminal how to start it again after
the terminal itself restarts, whether it crashed, the machine rebooted, or
the terminal was upgraded. The terminal keeps that declaration for the pane
and, when it restores the pane, runs the command. The program announces;
the terminal acts on its own schedule. Apart from an optional query, nothing
is sent back.

This summarizes version 1 of the specification as it stood at
[commit `aabe19c`](https://github.com/Otty-sh/osc-88/tree/aabe19cd7a581d4b9c2b7ae3454ca7d294f64d2c),
where its status is *Proposal*. Its authors name
[Otty](https://otty.sh) as the reference implementation and say the
protocol is not specific to it.

## The sequence

```
OSC 88 ; op [ ; key=value ]… ST
```

The first field after `88` is the operation. Fields after it are
`key=value` pairs separated by `;`, and a receiver splits each on its first
`=` only. Every value is the **base64** of a UTF-8 string, which keeps a `;`
from ever appearing inside one, except the two numeric fields, `v` and
`self_repaint`, which are written as plain digits. Either ST or BEL may end
the sequence; the specification prefers ST and requires a receiver to
accept both.

| Operation | Sent by | Meaning |
| --- | --- | --- |
| `arm` | the program | Set this pane's resume command, replacing any set before. It must have a `cmd`; one without is ignored. |
| `clear` | the program | Withdraw it, as on a clean exit. Any fields are ignored. |
| `query` | the program | Optional. Asks whether the terminal implements the protocol. |
| `supported` | the terminal | The answer to `query`: `OSC 88 ; supported ; v=N ST`, with `N` the highest version it implements. |

A program may send `arm` and `clear` without asking first: a terminal that
does not implement the protocol must ignore the whole sequence. One that
does should answer `query`, but no answer means only that support is
unknown, and the program must not wait for one indefinitely.

## The fields of `arm`

| Field | Value | Meaning |
| --- | --- | --- |
| `cmd` | base64, required | The program to run, and what the terminal verifies before trusting the rest |
| `args` | base64 | The rest of the command line, appended after `cmd` |
| `self_repaint` | `0` or `1` | `1`: the program redraws its own screen, so the terminal should not restore the pane's old contents as well. Default `0` |
| `cwd` | base64 | The directory to run in; otherwise the pane's last known working directory |
| `title` | base64 | A title for the restored pane, below [OSC 0 and 2](https://control-codes.page/osc/title/index.html.md) in priority |
| `v` | digits | The protocol version, `1` by default |

A receiver ignores keys it does not know, so that later versions can add
fields; a change that is not backward compatible must raise `v`. `args` is a
single string, and the specification does not say how it is split into
separate arguments.

For example, to have `htop -d 10` started again in `/home/alice`, with
`cmd` the base64 of `htop`, `args` of `-d 10`, `cwd` of `/home/alice`, and
`self_repaint=1` because `htop` draws its whole screen itself:

```
ESC ] 88 ; arm ; cmd=aHRvcA== ; args=LWQgMTA= ; cwd=L2hvbWUvYWxpY2U= ; self_repaint=1 ESC \
```

without the spaces, which are there only to make it readable, and
`ESC ] 88 ; clear ESC \` when it exits normally.

## Security

The terminal will later *run* what was armed, so the specification is
mostly about keeping text that merely passes through the terminal, a file
being `cat`ted, a log, a page of `man`, from arming a command. That kind of
escape-sequence injection is the threat it sets out to stop; a program that
can already run code in the pane is out of scope, since it can do worse
than arm a command. A terminal must:

- not run a resumed command without a check that defeats injection. The
  recommended check is to confirm, before keeping an `arm`, that a live
  process in the pane has an `argv[0]` whose basename is the basename of
  `cmd`; a `cat` printing forged bytes cannot claim another program's
  name. Another check is allowed if it proves the same thing.
- run only `cmd` as the program, with `args` strictly as its arguments, so
  that the unverified `args` can never change which program runs.
- show the resume to the user, and let them undo it.
- and should let the user turn resuming off, for every program or for
  particular ones.

## Open questions

The specification lists what may still change before version 1 is final:

- whether to add a `patch` operation that updates single fields, such as
  `cwd` as the program changes directory, instead of `arm` replacing
  everything;
- whether a program should be able to fix `cwd` so that the terminal may
  not substitute its own, more recent idea of it;
- whether `query` should be answered in the style of [DECRQM](https://control-codes.page/csi/decrqm/index.html.md)
  instead of with an OSC.

The number 88 itself is proposed, not settled: the specification found no
terminal using OSC 88, and asks for the number to be coordinated through the
community registry at [terminfo.dev](https://terminfo.dev/osc).

---

This is the Markdown version of <https://control-codes.page/proposals/osc-88/>. On that page every validation case runs live in libghostty-vt, the terminal emulation core of Ghostty, compiled to WebAssembly.
