#14823

GTK: `--gtk-single-instance` launch sometimes opens no window; the remote instance exits before its Activate call is sent

Openjcollieopened 6d ago

Discussed in https://github.com/ghostty-org/ghostty/discussions/14822

Originally posted by AlJohri October 23, 2026

Issue Description

With gtk-single-instance enabled, launching Ghostty while an instance is already running sometimes does nothing. No window opens, and the launching process exits 0. On my laptop this happens to roughly 1 in 30 Super+Enter presses (Hyprland, xdg-terminal-exec); on a slower machine it is more often.

A busctl monitor on /com/mitchellh/ghostty shows the difference. A launch that works sends org.gtk.Actions.DescribeAll and then org.gtk.Application.Activate. A launch that fails sends DescribeAll and never sends Activate.

The cause: on a remote instance, gio.Application.activate() only queues the Activate call (GLib's g_application_impl_activate is a fire-and-forget g_dbus_connection_call). Then Application.run returns and the process exits, sometimes before the GDBus worker thread writes the message. g_application_run flushes the session bus before it returns (g_application_impl_flush, gio/gapplication.c), but Ghostty runs its own loop and never flushes.

This looks different from #13554, where the request reaches a long-running instance that then makes no surface.

Proposed Fix

Flush the session bus before the remote path returns. With this, the reproduction below loses no activations.

Commit: https://github.com/AlJohri/ghostty/commit/6be6267eff23612158089aa2c5d136821acd9b4b

--- a/src/apprt/gtk/class/application.zig
+++ b/src/apprt/gtk/class/application.zig
@@ -556,6 +556,23 @@ pub const Application = extern struct {
                 "application is remote, exiting run loop after activation",
                 .{},
             );
+
+            // On a remote instance, activate() only queues an asynchronous
+            // D-Bus call. Flush the connection so that the call is sent
+            // before the process exits.
+            if (self.as(gio.Application).getDbusConnection()) |dbus| {
+                var flush_err: ?*glib.Error = null;
+                if (dbus.flushSync(null, &flush_err) == 0) {
+                    if (flush_err) |err| {
+                        defer err.free();
+                        log.warn(
+                            "error flushing D-Bus connection: {s}",
+                            .{err.f_message orelse "(unknown)"},
+                        );
+                    }
+                }
+            }
+
             return;
         }
 

Expected Behavior

Each launch of a remote instance opens a window in the primary instance.

Actual Behavior

Some launches send no Activate call. The process exits 0 and no window opens.

Reproduction Steps

The race depends on thread timing, so the reproduction makes the client compete for one CPU core. It does not need a running Ghostty, and it opens no windows: a stand-in primary on a private session bus counts the activations.

fake-primary.py:

import signal
from gi.repository import Gio, GLib

count = 0
app = Gio.Application(application_id="com.mitchellh.ghostty")

def on_activate(_app):
    global count
    count += 1

def on_term():
    print(count, flush=True)
    loop.quit()

app.connect("activate", on_activate)
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGTERM, on_term)
loop = GLib.MainLoop()
app.register(None)
loop.run()

Run from a Wayland session:

taskset -c 0 sh -c 'while :; do :; done' &   # keep core 0 busy
dbus-run-session -- sh -c '
  python3 fake-primary.py > count.txt & sleep 1
  for i in $(seq 200); do taskset -c 0 ghostty --gtk-single-instance=true >/dev/null 2>&1; done
  sleep 1; kill -TERM $!; wait'
kill %1
cat count.txt   # expect 200

Results, 2 runs of 200 launches each, every launch exited 0:

BuildActivations lost
main 22f100c40, 64
main 22f100c + patch0, 0
1.3.1 (nixpkgs)69, 106
1.3.1 (nixpkgs) + patch0, 0

Without the busy loop, Arch's 1.3.1 build still lost 19 of 200 when pinned to one core.

Ghostty Logs

Nothing from Ghostty points at the failure. The client's output is the same for a launch that works and a launch that fails, and it exits 0.

Ghostty Version

Ghostty 1.3.1+omarchy.3 (Arch), also main 22f100c built with the repo's flake

OS Version Information

Arch Linux, kernel 7.2.8; glib2 2.90.1, gtk4 4.24.1, libadwaita 1.10.0. Also on NixOS (glib 2.88.3).

(Linux only) Display Server

Wayland

(Linux only) Desktop Environment/Window Manager

Hyprland 0.56.2

Minimal Ghostty Configuration

(empty; the bug is in the remote launch path)

I used Claude Code to trace the D-Bus traffic, write the reproduction, and draft the patch. I reviewed and edited this text and the patch myself.

Activity

0

No activity yet.