# Semantic Block Query (?2034, SBQUERY)

> A proposal for a program to ask its terminal, as JSON, for the commands the shell has run in it: each command line, its prompt, its output and its exit code.

- **Sequence:** `CSI > Ps ; Pn ; T1 ; T2 ; T3 ; T4 b`
- **?2034**, Semantic Block Protocol: `CSI ? 2034 h/l`
- **Specification:** [Semantic Block Query (DEC Mode 2034), Contour VT extensions](https://github.com/contour-terminal/contour/blob/24072f86fd431fdb6ed24f546b9803558c020ca6/docs/vt-extensions/semantic-block-query.md)

*Semantic Block Query* lets a program ask its terminal for the commands run
in it, as structured data rather than as text scraped off the screen: for
each command, the command line, the prompt it was typed at, what it printed,
and how it exited. It is meant for programs that read a terminal session
from the outside, such as AI agents, accessibility tools and automation
scripts. A terminal can only know where one command ends and the next
begins if the shell tells it, so it relies on the shell marking its prompts,
command lines and output with [OSC 133](https://control-codes.page/osc/prompt/index.html.md).

It is Contour's. Its definition is
[Semantic Block Query](https://github.com/contour-terminal/contour/blob/24072f86fd431fdb6ed24f546b9803558c020ca6/docs/vt-extensions/semantic-block-query.md),
kept with Contour's other VT extensions, read at commit `24072f8`. Contour
has implemented it since February 2026 and shipped it in release 0.6.3.
The specification has been made more precise since without changing the
sequences, and the version read here is the one in release 0.8.0. No other
terminal is known to, and libghostty-vt does not recognize
mode `2034`.

## Turning it on

```
CSI ? 2034 h
```

Setting private mode `2034` makes the query available, and the terminal
answers the [DECSET](https://control-codes.page/csi/decset/index.html.md) itself, which a mode normally never does,
with a session token:

```
DCS > 2034 ; 1 b T1 ; T2 ; T3 ; T4 ST
```

The token is 64 random bits, written as four decimal numbers from `0` to
`65534`, each a 16-bit part. A part is never `65535`, because a terminal
reads any larger number in a parameter as `65535`, so that value cannot be
told apart from the numbers above it. Every query has to carry the token
back as its last four parameters, exactly as received. The specification
asks for a cryptographically secure random number generator, and a new
token each time the mode is set.

`CSI ? 2034 l` takes the query away and invalidates the token, and so does
[RIS](https://control-codes.page/esc/ris/index.html.md). Setting the mode again makes a new token, and the old one
no longer works. [DECRQM](https://control-codes.page/csi/decrqm/index.html.md) reports the mode as usual, so
`CSI ? 2034 $ p` is how a program finds out whether its terminal has it.

The mode also bounds what can be read. Once it is set, the terminal reports
only the commands that had not yet started: the prompt on screen when the
mode was set, and every command after it. A command that started earlier is
never reported, and resetting the mode hides everything so far even from a
later reader that sets it again, so setting the mode is never a way to read
back what ran before it.

## The query

```
CSI > Ps ; Pn ; T1 ; T2 ; T3 ; T4 b
```

The `>` keeps it apart from [REP](https://control-codes.page/csi/rep/index.html.md), `CSI Pn b`. `Ps` says what is
asked for:

| `Ps` | Asks for | `Pn` |
| --- | --- | --- |
| `1` | the last completed command | ignored |
| `2` | the last `Pn` completed commands | the count, `1` by default |
| `3` | the command running now | ignored |

A `Ps` of `0`, or none, means `1`. Any other `Ps` is answered as a query
with nothing to report, so a program asking something the terminal does not
know never gets the answer to another question. A command is *completed*
when the shell has ended it with `OSC 133 ; D`, or with
[OSC 3008](https://control-codes.page/osc/context/index.html.md)'s `end=`. One that a newer prompt replaced without
being ended, such as the prompt of a shell started inside `ssh`, is never
reported.

## The answer

```
DCS > Ps b JSON ST
```

| `Ps` | Means |
| --- | --- |
| `0` | the mode is reset, there is nothing to report, or the query type is unknown |
| `1` | success; the JSON follows |
| `2` | the token was missing |
| `3` | the token was wrong |

Only a success carries anything after the `b`. A token with a part left
out, or a part of `65535` or more, is a wrong token, `3`. The JSON is one
object, sent with no whitespace between its parts:

```
{"version":1,"blocks":[{"command":"echo hello","prompt":"user@host:~$ echo hello","output":"hello","exitCode":0,"finished":true,"outputLineCount":1}]}
```

| Field | Type | Meaning |
| --- | --- | --- |
| `version` | integer | the schema's version, `1` |
| `blocks` | array | the commands, oldest first |
| `command` | string or null | the command line from `OSC 133 ; C`'s `cmdline_url` or `cmdline`; failing that, what was typed after a prompt the shell ended with `OSC 133 ; B`; `null` if neither is known |
| `prompt` | string | the prompt with the command typed at it: the lines from the one `OSC 133 ; A` marked down to the one the output starts on |
| `output` | string | what the command printed: the lines from `OSC 133 ; C` to `OSC 133 ; D`, or, while it runs, down to the cursor's line |
| `exitCode` | integer | from `OSC 133 ; D` or `OSC 3008`'s `end=`; `-1` if unknown |
| `finished` | boolean | `false` for the running command, `true` otherwise |
| `outputLineCount` | integer | how many lines `output` has |

A command line longer than 4096 bytes is cut at a character boundary and
ends in `…`. The prompt and output are read from the command's own lines,
wherever they are in the scrollback, so neither carries another command's
text. Lines that have scrolled out of the history are missing from them,
and once all of a command's lines have, both are empty. Control characters
in the text are escaped as JSON escapes them, `\u001b` for `ESC`, so the
answer can never contain an `ST` that would end it early. How many
completed commands a terminal keeps is up to it.

For example, after `ls` and `echo hello` at a prompt, with the token from
setting the mode:

```
CSI ? 2034 h
    DCS > 2034 ; 1 b 41394 ; 50132 ; 58870 ; 1816 ST
CSI > 1 ; 1 ; 41394 ; 50132 ; 58870 ; 1816 b
    DCS > 1 b {"version":1,"blocks":[{"command":"echo hello",…}]} ST
CSI > 2 ; 3 ; 41394 ; 50132 ; 58870 ; 1816 b
    DCS > 1 b {…} ST, with ls, echo hello and the query before
CSI ? 2034 l
```

## Multiplexers

A reply to DECSET is something a terminal multiplexer such as tmux or
screen does not expect, and it may not pass the token on to the program
that set the mode. Without it the program cannot make a query at all. The
specification calls this a known limitation and asks multiplexers and
other programs that stand between a terminal and its applications to
forward the reply.

---

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