What happened
Hermes Agent (and other MCP hosts) spawn stdio MCP servers with a filtered environment to avoid leaking credentials: only a safe baseline passes (PATH, HOME, USER, LANG, LC_ALL, TERM, SHELL, TMPDIR, XDG_*). DBUS_SESSION_BUS_ADDRESS is stripped.
The guard in deskwright/shell.py (_gdbus) refuses before attempting to connect when the env var is unset:
if not os.environ.get("DBUS_SESSION_BUS_ADDRESS"):
raise ToolError("DBUS_SESSION_BUS_ADDRESS is not set, so the session bus is unreachable. ...")
Result: every extension/window tool call fails with extension_unavailable on the user's own, perfectly working desktop.
Why the guard over-triggers
The env var is an address hint, not the mechanism. Since the systemd user bus, GLib (and gdbus, and AT-SPI) connect to $XDG_RUNTIME_DIR/bus when the variable is absent — and XDG_RUNTIME_DIR survives every sanitized baseline.
Scripted reproducer
No MCP client, no dependencies. Run from a deskwright checkout, in the user's graphical session:
#!/usr/bin/env python3
"""Reproducer: the session-bus guard refuses a desktop it can actually reach."""
import os
import subprocess
import sys
BASELINE = ("PATH", "HOME", "USER", "LANG", "LC_ALL", "TERM", "SHELL", "TMPDIR",
"XDG_RUNTIME_DIR", "XDG_CONFIG_HOME", "XDG_DATA_HOME",
"XDG_STATE_HOME", "XDG_CACHE_HOME")
sanitized = {k: os.environ[k] for k in BASELINE if k in os.environ}
sanitized.pop("DBUS_SESSION_BUS_ADDRESS", None) # what the host strips
print(f"[1] sanitized env ({len(sanitized)} vars), "
"DBUS_SESSION_BUS_ADDRESS absent:",
"DBUS_SESSION_BUS_ADDRESS" not in sanitized)
probe = subprocess.run(
["gdbus", "introspect", "--session", "--dest", "org.freedesktop.DBus",
"--object-path", "/org/freedesktop/DBus"],
env=sanitized, capture_output=True, text=True, timeout=15)
if probe.returncode != 0:
sys.exit("Prerequisite not met: no session bus reachable even via the "
f"XDG_RUNTIME_DIR fallback on this machine:\n{probe.stderr}\n"
"(This reproducer needs the user's graphical session.)")
print("[2] gdbus reaches the session bus in that env: yes")
code = (
"from deskwright.shell import _gdbus\n"
"try:\n"
" _gdbus('Ping')\n"
" print('[3] OK: the guard let the call through to the extension')\n"
"except Exception as e:\n"
" print(f'[3] REPRODUCED: guard refused a reachable desktop: {e}')\n"
)
r = subprocess.run([sys.executable, "-c", code], env=sanitized,
capture_output=True, text=True, timeout=30)
print(r.stdout.strip() or r.stderr.strip())
Observed on Ubuntu 26.04, GNOME Wayland:
origin/main: step 2 succeeds, step 3 prints REPRODUCED: guard refused a reachable desktop: DBUS_SESSION_BUS_ADDRESS is not set... — the contradiction is the bug.
- fix branch (
timrichardson:fix/session-bus-fallback): [3] OK: the guard let the call through to the extension.
Step 2 succeeding while step 3 fails is the whole argument: same environment, same machine, gdbus reaches the bus the guard says is unreachable.
On a machine with no graphical session the script exits early with Prerequisite not met, so it can't mislead a reviewer on a headless box.
Second instance of the same pattern
execution.session_key() hashes the raw env var ("unbound" fallback) for desktop-identity/lease keys. A fallback-connected server and an explicit-connected server on one desktop get different keys.
Fix direction
Resolve the reachable bus (explicit address → $XDG_RUNTIME_DIR/bus socket) and refuse only when neither exists — the bare ssh login / system service case the diagnostic exists for. PR incoming on top of this issue.
What happened
Hermes Agent (and other MCP hosts) spawn stdio MCP servers with a filtered environment to avoid leaking credentials: only a safe baseline passes (
PATH,HOME,USER,LANG,LC_ALL,TERM,SHELL,TMPDIR,XDG_*).DBUS_SESSION_BUS_ADDRESSis stripped.The guard in
deskwright/shell.py(_gdbus) refuses before attempting to connect when the env var is unset:Result: every extension/window tool call fails with
extension_unavailableon the user's own, perfectly working desktop.Why the guard over-triggers
The env var is an address hint, not the mechanism. Since the systemd user bus, GLib (and
gdbus, and AT-SPI) connect to$XDG_RUNTIME_DIR/buswhen the variable is absent — andXDG_RUNTIME_DIRsurvives every sanitized baseline.Scripted reproducer
No MCP client, no dependencies. Run from a deskwright checkout, in the user's graphical session:
Observed on Ubuntu 26.04, GNOME Wayland:
origin/main: step 2 succeeds, step 3 printsREPRODUCED: guard refused a reachable desktop: DBUS_SESSION_BUS_ADDRESS is not set...— the contradiction is the bug.timrichardson:fix/session-bus-fallback):[3] OK: the guard let the call through to the extension.Step 2 succeeding while step 3 fails is the whole argument: same environment, same machine,
gdbusreaches the bus the guard says is unreachable.On a machine with no graphical session the script exits early with
Prerequisite not met, so it can't mislead a reviewer on a headless box.Second instance of the same pattern
execution.session_key()hashes the raw env var ("unbound"fallback) for desktop-identity/lease keys. A fallback-connected server and an explicit-connected server on one desktop get different keys.Fix direction
Resolve the reachable bus (explicit address →
$XDG_RUNTIME_DIR/bussocket) and refuse only when neither exists — the bare ssh login / system service case the diagnostic exists for. PR incoming on top of this issue.