Skip to main content
ansicode

OSC 4 query — Read 256-color palette index (`\x1b]4;<n>;?\x07`)

Ask the terminal what RGB value is currently bound to palette index `n` (0–255) — used by theme inspectors, screenshot tools, and palette-migration scripts.

Byte forms

Every common string-literal form so you can paste-and-search either direction.

\\x1b[\x1b]4;<n>;?\x07
\\033[\033]4;<n>;?\007
\\e[\e]4;<n>;?\a
ESC [ESC ] 4 ; n ; ? BEL
hex1b 5d 34 3b ... 3b 3f 07

Description

The *query* counterpart to OSC 4 (see `osc-set-palette` for the set side). Send `\x1b]4;<n>;?\x07` where `<n>` is the palette index 0–255 and the terminal replies on stdin with `\x1b]4;<n>;rgb:RRRR/GGGG/BBBB\x07` — same XParseColor-shaped, 16-bit-per-channel form used by the OSC 10 / 11 / 12 query (see `osc-color-query`). Multiple indices can be batched in one request — `\x1b]4;0;?;1;?;2;?\x07` returns three replies back-to-back. **Use cases**: - **Theme inspectors / dotfile generators** — dump the live 16 ANSI colors plus the 6×6×6 cube to YAML / JSON so a screenshot-faithful clone can be reproduced on a fresh machine. - **Screenshot / asciinema-style recorders** — capture the running palette so playback under a different theme still reproduces the original visuals. - **Palette migration scripts** — read out the current colors before applying a new theme, so the old one can be restored verbatim (a defensive `osc-reset-palette` only restores the *built-in* defaults, not whatever the user customised). - **CI test runners** — assert that a custom OSC 4 set actually took effect, by querying back the index immediately afterwards. **Reply parsing** — the index in the reply matches the index in the request, so when batching multiple `;<n>;?` pairs you must demultiplex by `<n>` (replies are not guaranteed to arrive in send order on every terminal, though most do). Time-out the read at ~100 ms per index and treat missing replies as 'index not customised — defer to ANSI/256 defaults'. **Caveats** — Linux console / Windows cmd ignore the query and reply nothing (timeout-and-fall-back is the correct strategy). macOS Terminal replies only for indices 0–15. Modern emulators (xterm / iTerm2 / Kitty / Alacritty / WezTerm / Ghostty / Konsole) all reply for the full 0–255 range.

Spec citation: xterm-ctlseqs (OSC 4 query)

Parameters

nPalette index 0–255 (0–7 = basic ANSI, 8–15 = bright ANSI, 16–231 = 6×6×6 cube, 232–255 = grayscale ramp).
?Query sentinel — replaces the spec to request the current binding instead of changing it.

Examples

bash
# Read palette index 1 (red) and print it.\nold=$(stty -g); stty -echo raw min 0 time 1\nprintf '\033]4;1;?\007' > /dev/tty\nIFS= read -r -d $'\\a' reply < /dev/tty\nstty "$old"\necho "palette[1] = ${reply##*rgb:}"
python
# Dump first 16 ANSI colors as a Python dict.\nimport sys, termios, tty, select, re\nfd = sys.stdin.fileno(); old = termios.tcgetattr(fd); tty.setraw(fd)\ntry:\n    palette = {}\n    for i in range(16):\n        sys.stdout.write(f'\x1b]4;{i};?\x07'); sys.stdout.flush()\n        if select.select([fd], [], [], 0.1)[0]:\n            buf = b''\n            while not buf.endswith(b'\\x07'):\n                buf += sys.stdin.buffer.read1(64)\n            m = re.search(rb'rgb:([0-9a-f]+)/([0-9a-f]+)/([0-9a-f]+)', buf, re.I)\n            palette[i] = '#' + ''.join(g.decode()[:2] for g in m.groups())\n    print(palette)\nfinally:\n    termios.tcsetattr(fd, termios.TCSADRAIN, old)
go
// Query a single palette index. (raw stdin setup elided — see osc-color-query)\nfor _, idx := range []int{0, 1, 2, 3, 4, 5, 6, 7} {\n    fmt.Printf(\"\\x1b]4;%d;?\\x07\", idx)\n    // read reply with bufio.Reader.ReadString(0x07), parse rgb:RRRR/GGGG/BBBB\n}
javascript
// Batch query indices 0..15 in one write.\nprocess.stdin.setRawMode(true); process.stdin.resume();\nconst req = Array.from({length: 16}, (_, i) => `4;${i};?`).join(';');\nprocess.stdout.write(`\\x1b]${req}\\x07`);\nlet buf = '';\nprocess.stdin.on('data', chunk => {\n    buf += chunk.toString();\n    const matches = [...buf.matchAll(/\\]4;(\\d+);rgb:([0-9a-f]+)\\/([0-9a-f]+)\\/([0-9a-f]+)\\x07/gi)];\n    if (matches.length === 16) {\n        process.stdin.setRawMode(false); process.stdin.pause();\n        console.log(matches.map(m => [+m[1], `#${m[2].slice(0,2)}${m[3].slice(0,2)}${m[4].slice(0,2)}`]));\n    }\n});
c
/* Query a single palette index — read until BEL. */\nprintf(\"\\x1b]4;%d;?\\x07\", index); fflush(stdout);\nchar buf[128]; int n = read(0, buf, sizeof buf - 1);\nif (n > 0) { buf[n] = 0; /* parse rgb:RRRR/GGGG/BBBB out of buf */ }

Terminal support

xterm
yes
Linux console (fbcon)
no
macOS Terminal.app
partial
iTerm2
yes
Windows Terminal
yes
cmd.exe / ConPTY
no
kitty
yes
alacritty
yes
WezTerm
yes
Ghostty
yes
GNOME Terminal
partial
Konsole
yes
tmux
no
GNU screen
no

Related sequences