Repository navigation
Open
Labels
Description
Summary
Terminal replies to the OSC 4 palette query (ESC ] 4 ; N ; rgb:rrrr/gggg/bbbb ST) leak their tail into the prompt input as if the user had typed it. With theme.name = "system" the full palette is queried on startup, so :cccc/cccc/cccc (the ANSI color #7 / cursor color) is pre-filled into the prompt on every launch and gets submitted as a user message.
Environment
- opencode version: 2.1.1
- OS: Linux 7.0.0-38-generic (Ubuntu 24.04 kernel), x86_64
- Terminal:
TERM=xterm-256colorover SSH; the client terminal reports the Windows Console "Campbell" palette (fg#cccccc, bg#0c0c0c) and answersOSC 10/11/12/13..17/19andOSC 4;0..15withST-terminatedrgb:replies - Shell:
/usr/bin/zsh - Install/channel: latest
- Active plugins:
[email protected]
Reproduction
- Set
~/.config/opencode/cli.jsonto use the terminal-derived theme:{ "theme": { "name": "system" } } - Connect from a terminal that answers OSC color queries (
ESC ]10;?,ESC ]11;?,ESC ]4;N;?) withST(ESC \) terminated replies, e.g.ESC ]4;7;rgb:cccc/cccc/cccc ESC \. - Run
opencode(also reproducible withopencode --standalone). - The prompt input is already filled with
:cccc/cccc/cccc:cccc/cccc/cccc.
Instrumented capture of the same run:
script -I /tmp/in.log -O /tmp/out.log -q -c opencodeExpected Behavior
The prompt input is empty on startup; terminal color replies are consumed by the terminal-capability parser only.
Actual Behavior
The tail of the palette reply is inserted into the prompt as text. If the user presses Enter, it is sent as a user message. In this report the leaked string was:
:cccc/cccc/cccc:cccc/cccc/cccc
Additional Context
/tmp/in.log(stdin) contains no typed bytes; only valid replies such as
\x1b]10;rgb:cccc/cccc/cccc\x1b\\,\x1b]11;rgb:0c0c/0c0c/0c0c\x1b\\,\x1b]4;7;rgb:cccc/cccc/cccc\x1b\\./tmp/out.log(stdout) shows the TUI rendering:cccc/cccc/ccccinside the input area.- Replaying the captured stdin into a pty reproduces the leak.
- Bisecting the captured stdin by sequence type: injecting only the
OSC 4;Npalette replies reproduces the leak (3 occurrences), while injecting onlyOSC 10/11/12/17/19color replies does not. - The parser appears to consume
ESC ]4;7;rgband drop the terminator, leaving:cccc/cccc/ccccbehind. - Query volume depends on the theme: with
theme.name = "system"the run issued 48OSC 4;Nqueries (3 full sweeps); with a built-in theme andmode: "dark"only ~2. - Workaround: running inside
tmuxavoids the issue (tmux answers the color queries itself and does not forward the outer terminal replies). - Not reproducible in a pty that answers each query exactly once; it appears when the terminal sends additional/repeated palette replies.
Activity
opencode-agent commentedon Oct 20, 2026
Thanks for the detailed report and the captures. I was able to reproduce this on 2.1.1, and it still happens on the latest
dev(d898c04).I ran
opencodein a PTY with{"theme":{"name":"system"}}and a script acting as the terminal, answering the OSC 10/11/12–19 andOSC 4;N;?queries with ST-terminatedrgb:replies:ESC]4;N;rgbfirst, then:rrrr/gggg/bbbb ESC\), the tail ends up in the prompt. This happens even when the second piece follows only 20 ms later. With a full palette sweep the prompt fills with:0000/0000/0000:1010/1010/1010…:cccc/cccc/cccc….So the trigger looks like a palette reply arriving split across reads, which is likely over SSH, rather than duplicate replies. It is closely related to #57345, which shows the same leak for a delayed OSC 11 reply. Here OSC 4 palette replies leak even with a very short gap.
The script below reproduces it (
python3 repro.py gap0.02leaks,python3 repro.py onceis the control). There's no video because our TUI automation can't inject raw terminal reply bytes. As you found, running inside tmux avoids it for now.Reproduction script (repro.py)
Run it:
python3 repro.pydqbpqdpb commentedon Oct 23, 2026
Anthony14FR commentedon Nov 12, 2026
I'd like to take this along with #57345: same split-reply leak, and the fix most likely belongs in the
@opentui/corestdin parser. I asked on #57345 where you'd like it fixed; I'll handle both there once that's settled.