Update:
I managed to find the source of the issue.
With an isolated Ghostty (debug build) instance launched using:
ghostty --config-default-files=false --window-theme=darkGhostty's GTK log correctly reports:
debug(gtk_ghostty_application): style manager changed scheme=.darkHowever, querying the current color scheme with CSI ? 996 n returns:
CSI ? 997 ; 2 nwhich indicates light mode.
I was able to narrow this down further by configuring an actual conditional theme pair:
theme = light:Rose Pine Dawn,dark:Rose PineWith that configuration, the same query correctly returns:
CSI ? 997 ; 1 nThe issue appears to be in Config.changeConditionalState. The function only rebuilds the configuration when the changed state key is present in _conditional_set. A single, non-conditional theme such as:
theme = Kitty Defaultdoes not add .theme to that set. As a result, changing the application color scheme from the default .light state to .dark is treated as irrelevant and the function returns null.
The surface and GTK runtime therefore know that the color scheme is dark, but the configuration copied into Termio retains its default _conditional_state.theme = .light. Since the mode-2031 response reads the theme from the Termio configuration, it reports light.
The following minimal patch treats the theme state as relevant even when the configured theme itself is not conditional, and updates the existing test accordingly:
diff --git a/src/config/Config.zig b/src/config/Config.zig
--- a/src/config/Config.zig
+++ b/src/config/Config.zig
@@ -4416,7 +4416,7 @@
// Conditional set contains the keys that this config uses. So we
// only continue if we use this key.
- if (self._conditional_set.contains(key) and !deepEqual(
+ if ((key == .theme or self._conditional_set.contains(key)) and !deepEqual(
@TypeOf(@field(self._conditional_state, field.name)),
@field(self._conditional_state, field.name),
@field(new, field.name),
@@ -10587,7 +10587,7 @@ test "changed" {
try testing.expect(!source.changed(&dest, .@"font-size"));
}
-test "changeConditionalState ignores irrelevant changes" {
+test "changeConditionalState preserves non-conditional theme state" {
const testing = std.testing;
const alloc = testing.allocator;
@@ -10600,9 +10600,12 @@ test "changeConditionalState ignores irrelevant changes" {
try cfg.loadIter(alloc, &it);
try cfg.finalize();
- try testing.expect(try cfg.changeConditionalState(
- .{ .theme = .dark },
- ) == null);
+ var cfg2 = (try cfg.changeConditionalState(.{
+ .theme = .dark,
+ })).?;
+ defer cfg2.deinit();
+
+ try testing.expectEqual(.dark, cfg2._conditional_state.theme);
}
}I changed the existing changeConditionalState test because it explicitly asserted the behavior responsible for the bug: a light-to-dark state change with a non-conditional theme was expected to return null. That assumption is no longer valid because the theme state is observable through mode 2031 even when it does not change any configured values. The revised test therefore verifies that the new configuration preserves _conditional_state.theme = .dark.
The drawback is that this replaces the test coverage for the original optimization, where genuinely irrelevant conditional-state changes should still return null. If there is a better way to implement a new test for this, I would defer to that instead.
This was reproduced against commit 6687d60.
Originally posted by @UnsaltedScholar in https://github.com/ghostty-org/ghostty/discussions/13448#discussioncomment-17889061
Activity
5Confirming that this also affects macOS (not only GTK).
Environment:
- Ghostty 1.3.1 stable
- macOS 26.6.2 (25G83), system appearance: Dark
- Default Ghostty config (no user overrides):
background = #282c34,window-theme = auto - pi 0.84.2 as the downstream application
In a fresh Ghostty window, I queried both mechanisms directly:
CSI ? 996 n -> CSI ? 997 ; 2 n # light
OSC 11 -> ESC ] 11 ; rgb:2828/2c2c/3434 BEL # #282c34, darkThis causes pi's automatic light/dark theme setting to select light, because it correctly gives the explicit color-scheme report precedence over the OSC 11 background response.
So the same stale conditional_state.theme = .light behavior is reproducible on macOS with the current stable Ghostty release. PR #13605 appears to address the underlying issue.
Confirming on Linux/GTK, with one refinement to the workaround.
Environment
- Ghostty 1.3.1-3.fc44 (Fedora 44, Terra), GTK, Wayland
- KDE Plasma. The portal reports dark:
org.freedesktop.appearance color-schemereads1(prefer-dark), and GTK is atgtk-application-prefer-dark-theme=true - Colours set directly in the config (
background = #0b0e0b,foreground,palette), with notheme =line at all
CSI ? 996 n returns CSI ? 997 ; 2 n (light) while OSC 11 returns the dark background, as in the macOS report above. Unchanged by --window-theme=light|dark|system, by --background=000000 or --background=ffffff, by GTK_THEME=Adwaita:dark, and by ADW_DEBUG_COLOR_SCHEME=prefer-dark. All re-run with --gtk-single-instance=false, so the flags are not being swallowed by an already-running instance.
The refinement: the conditional-theme-pair workaround only works if the two themes differ.
| config | reply |
|---|---|
theme = light:embermoss,dark:embermoss | CSI ? 997 ; 2 n (still light) |
theme = light:embermoss-light,dark:embermoss | CSI ? 997 ; 1 n (correct) |
Both reproduced twice on the same build in the same session, with the two theme files differing only in background and foreground. Naming one theme on both sides is apparently not enough to get .theme into _conditional_set, which matters for anyone reaching for the workaround who only has one theme they actually want: they have to invent a second one that differs.
Downstream effect, in case it is useful for weighing #13605: yazi 26.8 chooses between its dark and light flavours on exactly this report. A flavour configured only for dark therefore vanishes inside Ghostty, while staying correct in Konsole, which does not implement the query and falls back to OSC 11, and inside tmux, which answers 997;1.
Confirming on Linux/GTK, with one refinement to the workaround.
Environment
- Ghostty 1.3.1-3.fc44 (Fedora 44, Terra), GTK, Wayland
- KDE Plasma. The portal reports dark:
org.freedesktop.appearance color-schemereads1(prefer-dark), and GTK is atgtk-application-prefer-dark-theme=true - Colours set directly in the config (
background = #0b0e0b,foreground,palette), with notheme =line at all
CSI ? 996 n returns CSI ? 997 ; 2 n (light) while OSC 11 returns the dark background, as in the macOS report above. Unchanged by --window-theme=light|dark|system, by --background=000000 or --background=ffffff, by GTK_THEME=Adwaita:dark, and by ADW_DEBUG_COLOR_SCHEME=prefer-dark. All re-run with --gtk-single-instance=false, so the flags are not being swallowed by an already-running instance.
The refinement: the conditional-theme-pair workaround only works if the two themes differ.
| config | reply |
|---|---|
theme = light:embermoss,dark:embermoss | CSI ? 997 ; 2 n (still light) |
theme = light:embermoss-light,dark:embermoss | CSI ? 997 ; 1 n (correct) |
Both reproduced twice on the same build in the same session, with the two theme files differing only in background and foreground. Naming one theme on both sides is apparently not enough to get .theme into _conditional_set, which matters for anyone reaching for the workaround who only has one theme they actually want: they have to invent a second one that differs.
Downstream effect, in case it is useful for weighing #13605: yazi 26.8 chooses between its dark and light flavours on exactly this report. A flavour configured only for dark therefore vanishes inside Ghostty, while staying correct in Konsole, which does not implement the query and falls back to OSC 11, and inside tmux, which answers 997;1.
Just for the record, the fact this is an issue means that we have a reliable way to reproduce it already. We therefore don't really need other reproductions and you should shift your attention to testing the associated PR instead: #13605
Confirming that this also affects macOS (not only GTK).
Environment:
background = #282c34,window-theme = autoIn a fresh Ghostty window, I queried both mechanisms directly:
This causes pi's automatic
light/darktheme setting to selectlight, because it correctly gives the explicit color-scheme report precedence over the OSC 11 background response.So the same stale
conditional_state.theme = .lightbehavior is reproducible on macOS with the current stable Ghostty release. PR #13605 appears to address the underlying issue.