1jehuang / 1jehuang/jcode

Startup theme auto-detection still writes raw OSC 11 query bytes to the terminal

Open
#1,004 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

autonomous: no bug triage: needs-decision
Dominant language
Rust
Stars
19.9k
Forks
2.3k
Avg merge
2d 7h
Merged PRs (30d)
30

Description

Summary

jcode still writes an OSC 11 background-color query to the terminal during interactive startup on current upstream master.

On terminals, wrappers, or host environments that do not safely consume that query, the raw control bytes become visible to the user as startup garbage.

This is not the older dual-reply/composer-corruption bug fixed by e67d74e47. That fix reduced the probe from OSC 10 + OSC 11 to OSC 11 only, but the remaining startup-side write is still observable and still user-visible on some terminals.

Current upstream status

Verified against upstream default branch master at:

  • commit: 70dcb085d (origin/master as fetched on 2026-08-18 23:42 UTC)

Relevant code on current upstream:

  • crates/jcode-tui/src/tui/theme_detect.rs:158-214
  • specifically:
    • :163-171 capability gate terminal_background_query_supported(...)
    • :191-199 call to terminal_colorsaurus::background_color(options)

The latest upstream still performs the startup-time background query.

Repro steps

Interactive TTY repro, using a clean JCODE_HOME and telemetry disabled so the startup bytes are easy to inspect:

JCODE_HOME=/tmp/jcode-issue-home \
JCODE_NO_TELEMETRY=1 \
TERM=xterm-256color \
TERM_PROGRAM=Orca \
python3 - <<'PY'
import os, pty, select, subprocess, time
master, slave = pty.openpty()
env = os.environ.copy()
p = subprocess.Popen(['jcode'], stdin=slave, stdout=slave, stderr=slave, env=env, close_fds=True)
os.close(slave)
end = time.time() + 0.9
buf = bytearray()
sent = False
while time.time() < end:
    r, _, _ = select.select([master], [], [], 0.05)
    if master in r:
        try:
            data = os.read(master, 65536)
        except OSError:
            break
        if not data:
            break
        buf.extend(data)
    if not sent and time.time() > end - 0.2:
        try:
            os.write(master, b'\x03')
            sent = True
        except OSError:
            pass
print(bytes(buf)[:120].hex())
PY

On current upstream master, the capture begins with:

1b5d31313b3f071b5b63

Which decodes to:

ESC ] 11 ; ? BEL ESC [ c

Meaning:

  • OSC 11 background-color query
  • immediately followed by DA1 / CSI c

Observed bytes

Raw injected bytes at startup:

1b 5d 31 31 3b 3f 07 1b 5b 63

Observed startup prefix from the current upstream build:

1b5d31313b3f071b5b63436f6e6e656374696e6720746f207365727665722e2e2e0d0a1b5b3f31303439681b5b3e37751b5b3f32303034681b5b3f31303034681b5b3f31303030681b5b3f31303032681b5b3f31303033681b5b3f31303135681b5b3f31303036681b5d303b6a636f646507...

So the query is emitted before normal TUI setup such as alternate-screen entry and title-setting.

Expected behavior

Startup should not emit raw terminal query bytes that can become visible to the user.

More concretely:

  • if jcode cannot prove that the host terminal safely supports this probe, it should not send the query
  • display.theme=auto should degrade to a safe default (dark) instead of writing potentially visible OSC bytes
  • startup should remain visually clean even on wrappers, multiplexers, remoting layers, or nonstandard terminal hosts

Actual behavior

On affected hosts, users can see visible startup garbage originating from the theme auto-detection query.

Current upstream no longer leaks the old dual-query OSC 10/OSC 11 behavior into the composer, but it still writes a single OSC 11 probe during startup, and that is enough to be user-visible in some environments.

Affected terminals / hosts

Confirmed affected environment during repro:

  • TERM=xterm-256color
  • TERM_PROGRAM=Orca

The core issue is broader than Orca specifically: the current gate treats any non-empty TERM_PROGRAM or LC_TERMINAL as a strong positive signal for OSC-query safety, but that does not guarantee the host will consume the query invisibly.

Root cause

terminal_background_query_supported(...) is too permissive:

if term_program.is_some_and(|value| !value.trim().is_empty())
    || lc_terminal.is_some_and(|value| !value.trim().is_empty())
{
    return true;
}

That logic answers “this looks like a terminal emulator” rather than the stronger property jcode actually needs here:

“sending an OSC 11 query during startup will not surface raw bytes to the user”

So display.theme=auto still enters the probe path in environments that are not safe for that startup write.

Suggested fix

Any of the following would fix the user-visible bug:

  1. Tighten the allowlist

    • Only send the startup OSC 11 query for terminals explicitly known to handle it safely.
    • Do not treat arbitrary TERM_PROGRAM / LC_TERMINAL values as sufficient proof.
  2. Default to safe fallback

    • If safety cannot be established, skip probing and default to ThemeMode::Dark.
  3. Keep the manual override as the escape hatch

    • JCODE_THEME=dark|light already avoids the probe path and can remain the explicit override.

Extra context

The earlier bug was introduced by 9e28c2bf5 (feat(tui): detect light terminal themes and adapt colors), which used terminal_colorsaurus::theme_mode(options) and therefore sent both OSC 10 and OSC 11.

That older behavior was partially fixed by e67d74e47 (fix(tui): prevent theme query replies entering composer), which switched the code to background_color(options) and removed the second query/reply leak.

However, the remaining single-query startup write is still observable and still looks like an upstream bug, because it violates the user-facing expectation that startup should not print raw terminal control bytes.


Drafted by gpt-5.4.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in crates/jcode-tui/src/tui/theme_detect.rs, especially terminal_background_query_supported(...) and the background_color(options) call around lines 163-199. Reproduce the startup bytes with the provided PTY script, then trace the auto-detection path and its existing tests or callers. Done means unsafe hosts skip the OSC 11 query, fall back to dark, and startup remains free of raw query bytes.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.