^[control-codes live in libghostty-vt

Proposals

?2034, SBQUERY Semantic Block Query

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.

SBQUERY Semantic Block Query

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

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.

It is Contour’s. Its definition is Semantic Block Query, 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 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. Setting the mode again makes a new token, and the old one no longer works. DECRQM 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, CSI Pn b. Ps says what is asked for:

PsAsks forPn
1the last completed commandignored
2the last Pn completed commandsthe count, 1 by default
3the command running nowignored

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’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
PsMeans
0the mode is reset, there is nothing to report, or the query type is unknown
1success; the JSON follows
2the token was missing
3the 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}]}
FieldTypeMeaning
versionintegerthe schema’s version, 1
blocksarraythe commands, oldest first
commandstring or nullthe 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
promptstringthe prompt with the command typed at it: the lines from the one OSC 133 ; A marked down to the one the output starts on
outputstringwhat the command printed: the lines from OSC 133 ; C to OSC 133 ; D, or, while it runs, down to the cursor’s line
exitCodeintegerfrom OSC 133 ; D or OSC 3008’s end=; -1 if unknown
finishedbooleanfalse for the running command, true otherwise
outputLineCountintegerhow 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.

Every example on these pages runs in your browser, in libghostty-vt compiled to WebAssembly.