Input box remain dark after the system theme turn to light

Open 💬 10 comments Opened Apr 22, 2026 by cynicalight
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

What version of Codex CLI is running?

0.122.0

What subscription do you have?

plus

Which model were you using?

gpt-5.4

What platform is your computer?

Darwin 25.3.0 arm64 arm

What terminal emulator and version are you using (if applicable)?

Ghostty + Tmux

What issue are you seeing?

After the system switch to light theme in another day morning, the input box of the codex cli is still in dark mode(last night is in dark mode). I have to /exit codex and reopen it again, the input box turns to light theme.

What steps can reproduce the bug?

  1. Switch to dark mode in mac, and open codex:

<img width="701" height="168" alt="Image" src="https://github.com/user-attachments/assets/20ed7b47-eb3f-41a6-945f-d64ad51405be" />

  1. then switch to light mode, and you will see the input box stay dark:

<img width="724" height="295" alt="Image" src="https://github.com/user-attachments/assets/c3c46d45-02a9-4cff-888b-c390d3de96a4" />

What is the expected behavior?

_No response_

Additional information

_No response_

View original on GitHub ↗

10 Comments

github-actions[bot] contributor · 4 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #16411

Powered by Codex Action

rushrs · 4 months ago

Proposed fix

I have a working branch with a narrow fix here:

The change does three things:

  • add a helper to reselect the adaptive runtime theme from the current terminal palette
  • call it after requery_default_colors() on FocusGained
  • leave explicit user-configured tui.theme overrides untouched

It also adds a focused unit test for the FocusGained event path.

Why this hook

FocusGained is already where Codex refreshes its view of the terminal palette, so it is the least invasive place to update the adaptive theme as well. This keeps the work off the render hot path and only runs when the terminal returns focus.

User impact

  • terminals that update their reported default colors after a system appearance switch can cause Codex to follow the new appearance without restarting
  • users with an explicit tui.theme setting see no behavior change
120318 · 3 months ago

any updates?

YounesElhjouji · 2 months ago

Same here, also with ghostty + tmux.

This one is a real bummer, sometimes quitting and reopening fixes it, but mostly does not, opening a new session also doesn't fix it.

duc-cloudthinker · 18 days ago

Still reproducible on codex-cli 0.147.0 (macOS arm64), and the cause on main is now different from the one this issue was filed against.

The re-query was removed. codex-rs/tui/src/tui/event_stream.rs:274 now does:

Event::FocusGained => {
    self.terminal_focused.store(true, Ordering::Relaxed);
    // Keep the startup-cached palette: querying terminal colors here blocks the
    // input loop, and a direct probe would discard keys typed during the refresh.
    Some(TuiEvent::Draw)
}

So the terminal's default colors are read exactly once, by the startup probe in codex-rs/tui/src/terminal_probe.rs (100 ms deadline), cached in a OnceLock in codex-rs/tui/src/terminal_palette.rs, and never refreshed for the life of the process. That makes the stale input box permanent in every terminal, not only after an OS appearance switch.

Measurement. I ran codex under a PTY harness that plays the host terminal and answers OSC 10/11, then counted the queries in the raw output:

| what the harness did | ESC]10;? | ESC]11;? | ESC[?2031h | composer background emitted |
| --- | --- | --- | --- | --- |
| answer rgb:2828/2a2a/3636 | 1 | 1 | never | ESC[48;2;65;67;78m |
| answer rgb:ffff/ffff/ffff | 1 | 1 | never | ESC[48;2;244;244;244m |
| answer 250 ms late | 1 | 1 | never | none |
| answer nothing | 1 | 1 | never | none |
| answer, then send ESC[O and ESC[I at t+8s | 1 | 1 | never | ESC[48;2;65;67;78m |

Codex enables focus reporting (ESC[?1004h) and the harness writes ESC[I to its input, and no second query appears. The composer background is user_message_bg_rgb in codex-rs/tui/src/style.rs, blended from the one cached value.

Repro without touching OS appearance. Start codex in a terminal whose theme is light, then switch that terminal to a dark theme. The composer keeps the light background while the text is drawn in the new light foreground, so the input line becomes unreadable. My case was Warp: it answers ESC]11;? with the active theme's background, and I changed themes 20 minutes into a session.

Suggested fix that does not reintroduce the blocking probe. Send ESC[?2031h at startup and update the cached palette when the terminal pushes CSI ?997;1n (dark) or CSI ?997;2n (light). That is push-based, so it costs nothing on the input loop, and it avoids the FocusGained freeze reported in #33037. Terminals that do not implement mode 2031 simply keep today's behavior.

Note for triage: #37298 proposes fixing requery_default_colors(), but that function no longer exists on main.

dandandy · 14 days ago

I’ve implemented and tested a reference fix for this:

on FocusGained Codex sends non-blocking OSC 10/11 queries. Responses are consumed through the existing Crossterm EventStream, avoiding a competing terminal reader and preserving input typed during the refresh. Once both colors arrive Codex updates the cached palette and redraws the TUI.

my reference fix branch:
https://github.com/dandandy/codex/tree/fix/live-terminal-palette-refresh

crossterm dependency PR:
https://github.com/openai-oss-forks/crossterm/pull/1

Validation: just test -p codex-tui — 3,447 tests passed, including PTY coverage confirming that palette queries occur when focus returns and immediate input is not lost.

I’m happy to open a Codex PR if I can get an to invite to contribute

everlof · 13 days ago

Independent reproduction on codex-cli 0.147.0 (macOS arm64), in a bare PTY with no tmux, no SSH and no terminal emulator in the path. This corroborates @duc-cloudthinker's reading of event_stream.rs:274 and separates this from the failure modes in #23489 and #37298.

The harness answers OSC 10/11 immediately (microseconds, far inside the 100 ms deadline), then changes the terminal's colours and sends a focus transition:

focus reporting enabled (DECSET 1004): True
OSC 10/11 queries (slot, seconds):     [('10', 0.67), ('11', 0.67)]
plates before, told bg #101060:        [('#2C2C73', 125)]
plates after,  told bg #FFFFFF + focus: [('#2C2C73', 388)]
control keystroke reached the TUI:     True

Three things that pins down:

  1. The cached palette is good, not poisoned. Answering #101060 produces a composer plate of #2C2C73, derived from that answer. So the startup probe succeeded and the cache holds a correct value. This is not #37298 (a failed probe overwriting a good value) and not #23489 (a probe that times out). The palette is right and simply never read again.
  2. The focus event is delivered and parsed. Codex sets DECSET 1004, and ZQZ written to the same fd right after the CSI I comes back echoed by the TUI. So the bytes arrive; FocusGained is received and deliberately ignored.
  3. Exactly one query per process lifetime, here at t≈0.7s. No later query under keyboard input, window resize or SIGWINCH.

Repro, no dependencies, works against any build:

#!/usr/bin/env python3
"""Minimal repro: does the Codex TUI ever re-read the terminal's colours?

A bare PTY, no tmux, no SSH, no terminal emulator. It answers OSC 10/11
immediately (microseconds, not the 100 ms deadline), so every timeout theory is
off the table. Usage:  python3 osc_repro.py /path/to/codex
"""
import fcntl, os, pty, re, select, struct, sys, termios, time
from collections import Counter

BIN = sys.argv[1] if len(sys.argv) > 1 else "codex"

pid, fd = pty.fork()
if pid == 0:
    os.environ["TERM"] = "xterm-256color"
    os.environ["COLORTERM"] = "truecolor"
    os.execvp(BIN, [BIN])
fcntl.ioctl(fd, termios.TIOCSWINSZ, struct.pack("HHHH", 40, 120, 0, 0))

# Phase 1 answers with a distinctive dark blue so a *detected* palette is
# impossible to confuse with the built-in dark fallback.
answer, fg = "1010/1010/6060", "e0e0/e0e0/e0e0"
before, after = b"", b""
queries, modes = [], set()
switched = False
start = time.time()

while time.time() - start < 14:
    ready, _, _ = select.select([fd], [], [], 0.2)
    now = time.time() - start

    if now > 6 and not switched:          # the terminal changes its colours
        switched = True
        answer, fg = "ffff/ffff/ffff", "1111/1111/1111"
        os.write(fd, b"\x1b[O")           # focus lost
        time.sleep(0.1)
        os.write(fd, b"\x1b[I")           # focus gained
        time.sleep(0.3)
        os.write(fd, b"ZQZ")              # control: does input reach the TUI?

    if not ready:
        continue
    try:
        data = os.read(fd, 65536)
    except OSError:
        break
    if not data:
        break
    if switched:
        after += data
    else:
        before += data

    for slot in re.findall(rb"\x1b\](1[01]);\?", data):
        queries.append((slot.decode(), round(now, 2)))
    for m in re.findall(rb"\x1b\[\?([\d;]+)h", data):
        modes.update(m.decode().split(";"))
    if b"\x1b]11;?" in data:
        os.write(fd, ("\x1b]11;rgb:%s\x1b\\" % answer).encode())
    if b"\x1b]10;?" in data:
        os.write(fd, ("\x1b]10;rgb:%s\x1b\\" % fg).encode())
    if b"\x1b[c" in data:
        os.write(fd, b"\x1b[?1;2c")
    if b"\x1b[6n" in data:
        os.write(fd, b"\x1b[1;1R")

try:
    os.kill(pid, 9)
except OSError:
    pass
os.close(fd)

def plates(buf):
    text = buf.decode("utf-8", "replace")
    return Counter("#%02X%02X%02X" % (int(r), int(g), int(b))
                   for r, g, b in re.findall(r"48;2;(\d+);(\d+);(\d+)", text)).most_common(3)

print("focus reporting enabled (DECSET 1004):", "1004" in modes)
print("OSC 10/11 queries (slot, seconds):    ", queries)
print("plates before, told bg #101060:       ", plates(before))
print("plates after,  told bg #FFFFFF + focus:", plates(after))
print("control keystroke reached the TUI:    ", b"ZQZ" in after)

One note for whoever picks up @dandandy's branch: restoring the re-query on FocusGained does not cover terminals whose theme is application-owned rather than OS-owned. Changing the theme in a focused window produces no focus transition at all, so the composer stays stale while the window never loses focus. DECSET 2031 is the mechanism designed for exactly that, and Codex does not set it today (strings on the 0.147.0 binary contains no 2031). Filed separately as #38575.

xudalin007 · 6 days ago

Codex CLI: 0.149.0
Platform: macOS
Behavior:
When macOS automatically switches from light mode to dark mode at night,
an already-running Codex CLI session does not refresh the TUI theme correctly.

The composer/input area becomes a large white/light block, while the typed text
is effectively invisible because of the foreground/background color conflict.

This happens reproducibly every night when macOS automatically changes appearance.

Workaround:

  1. Ctrl+C / exit Codex
  2. Run codex resume <session-id>
  3. The UI immediately renders correctly in dark mode.

Important:
The underlying Terminal renders correctly after the macOS appearance switch.
Only the already-running Codex TUI is affected.

Expected behavior:
Codex CLI should detect macOS/terminal appearance changes while running and
refresh the TUI palette automatically, without requiring the user to restart
the session.

<img width="1074" height="127" alt="Image" src="https://github.com/user-attachments/assets/f9aa595b-0c08-4737-89b0-28b725ecc987" />

arzafran · 4 days ago

another data point ruling out the terminal side: this repros in programa (libghostty-based terminal). when macos auto-switches between light and dark, the terminal swaps its default bg/fg immediately and emits the mode 2031 color scheme report, so the background under codex follows the new theme right away. codex keeps painting the palette it detected at startup, so after a dark to light flip you get light gray text on a light background, close to unreadable.

claude code running in the same terminal is unaffected because it draws with terminal defaults / ansi indices instead of fixed rgb.

subscribing to mode 2031 (CSI ? 2031 h and handling the unsolicited DSR report) or re-running theme detection when it arrives would fix live switching without needing a restart.

piotrostr · 1 day ago

Working automatic reference implementation covering this repro is posted here: https://github.com/openai/codex/issues/38575#issuecomment-5425261944

It includes both live DEC mode 2031 refresh and the stale composer foreground fix. Validated on Ghostty 1.2.3 through tmux 3.6a. No command or config surface.

~ codex & piotr