Skip to content

headless session: the AT-SPI registry never starts, so ui_* tools fail with 'no application named ...' (self-test 16/18) #3

Open

Description

@timrichardson

desktop_health output

From a headless session in the bug state, after a clean install, logged out and back in (extension ACTIVE, so this is not the usual "you didn't log out" case):

{
 "verdict": "USABLE on the default desktop with limits: the accessibility tree is empty -- toolkit-accessibility may have been off when these apps started. Screenshots, window control and pointer input all work.",
 "implementation": {
  "source": "/home/tim/projects/deskwright",
  "revision": "9624a33",
  "session": "headless:default"
 },
 "extension": "ACTIVE",
 "windows": 0,
 "atspi_apps": 0,
 "pointer_control": "absolute, via org.gnome.Mutter.RemoteDesktop; desktop is 1280x720 at (0, 0)"
}

Note the verdict blames toolkit-accessibility. That misdirection is part of why this is easy to miss.

Environment

  • GNOME Shell version: 50.1
  • Distribution: Ubuntu 26.04 (at-spi2-core 2.60.4, dbus-daemon 1.16.2, dbus-broker 37-5, systemd 259)
  • How is it installed?: git clone (reproduced identically with the 0.1.1 pipx install)
  • Which desktop was it driving?: a headless session (DESKWRIGHT_SESSION=headless)
  • Logged out and back in since installing: yes, extension reports ACTIVE.

What happened

DESKWRIGHT_SESSION=headless deskwright --self-test fails 2/18, always the same two:

FAIL  ui_tree(gnome-shell)     ToolError: no application named 'gnome-shell' on the AT-SPI bus. Present: .
FAIL  ui_find(actionable)      ToolError: no application named 'gnome-shell' on the AT-SPI bus. Present: .
16/18 passed

The cause is in the session's own log, and it is not about apps or trees: the headless session's a11y bus never had a registry at all. ~/.local/state/deskwright/headless-shell.log shows the a11y broker asking the private session bus to activate org.freedesktop.systemd1 (to start at-spi2-registryd), once per retry:

dbus-daemon[...]: Activating service name='org.freedesktop.systemd1' requested by ':1.7' (... comm="/usr/bin/dbus-broker-launch ...")
dbus-daemon[...]: Activated service 'org.freedesktop.systemd1' failed: Process org.freedesktop.systemd1 exited with status 1

The chain:

  1. The headless session runs a private dbus-daemon --session, which has no systemd behind it (that is the point of it).
  2. Since at-spi2-core 2.59.0 the a11y launcher is dbus-broker by default, and it routes service activation through systemd.
  3. On a bus with no systemd, org.freedesktop.systemd1 resolves to the stub systemd ships for systemd-less buses, Exec=/bin/false, which exits 1. So at-spi2-registryd is never started, org.a11y.atspi.Registry has no owner, and every ui_* call fails.

The user's real session never sees this because its bus has the real systemd behind the name. That asymmetry is presumably why this reads as "stunted trees" (the toolkit-accessibility explanation) rather than "no registry".

This looks version-triggered rather than Ubuntu-specific: the dbus-broker-by-default switch is at-spi2-core 2.59.0 (GNOME 49.1 era), so any current GNOME 50 stack should hit it. I have only measured Ubuntu 26.04; if anyone can run the repro below on Fedora/Arch it would confirm the range.

Reproduce it (scripted, no guessing)

On any GNOME Wayland login with the repo's dependencies present, from a fresh clone of main:

git clone https://github.com/tristanmuzzu/deskwright && cd deskwright
python3 - <<'PY'
import os, subprocess, sys
sys.path.insert(0, ".")
from deskwright import headless

st = headless.ensure(name="bugrepro")          # ~20 s first time
print("session running:", st["running"])
headless.pin_env(st, os.environ)               # ask FROM INSIDE the session

def gdbus(*args, addr=None):
    cmd = ["gdbus", "call", "--address", addr] if addr else ["gdbus", "call", "--session"]
    return subprocess.run(cmd + list(args), capture_output=True, text=True,
                          env=dict(os.environ, **({} if addr else {"DBUS_SESSION_BUS_ADDRESS": st["bus_address"]})))

# 1. the a11y bus itself is fine -- its launcher does not need systemd
out = gdbus("--dest", "org.a11y.Bus", "--object-path", "/org/a11y/bus",
            "--method", "org.a11y.Bus.GetAddress")
sock = out.stdout.strip().strip("()").strip("'\"").removeprefix("unix:path=")
print("a11y socket answers:", bool(sock), sock)

# 2. the registry name on that bus -- THIS is the bug: no owner
name = gdbus("--dest", "org.freedesktop.DBus", "--object-path", "/org/freedesktop/DBus",
             "--method", "org.freedesktop.DBus.GetNameOwner",
             "org.a11y.atspi.Registry", addr=sock)
print("registry owned:", name.returncode == 0, name.stderr.strip()[:80])

headless.stop("bugrepro")
sys.exit(0 if name.returncode == 0 else 1)
PY
echo "exit=$?"    # main: 'registry owned: False', exit=1

The same probe doubles as the acceptance check for a fix: exit 0 with registry owned: True. A second thing a fix must get right, measured while working around this by hand: the registry has to be up before gnome-shell starts. A registry that appears after the shell answers still leaves the self-test at 16/18, because the shell's atk-bridge stops retrying registration (apps started later were on the bus while gnome-shell was not, same session). A cold DESKWRIGHT_SESSION=headless deskwright --self-test should go 18/18.

Workaround for anyone blocked today: start registryd against the private bus by hand (it resolves the a11y socket itself, only DBUS_SESSION_BUS_ADDRESS is needed) and restart the headless session so the shell registers:

DBUS_SESSION_BUS_ADDRESS="$(python3 -c 'import json;print(json.load(open(".../.local/state/deskwright/headless-default.json"))["bus_address"])')" \
  /usr/libexec/at-spi2-registryd --use-gnome-session &   # keep it running

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions