fix(computer_use): surface daemon units pointing at pruned cua-driver releases in doctor

Linux has no managed cua-driver autostart, so daemon units are hand-written
against a concrete release directory. The installer prunes all but the last
five release dirs, so a versioned Exec reference crash-loops with 203/EXEC
after every upgrade while every binary-level check stays green (#114748).

doctor now scans systemd user units and XDG autostart entries for cua-driver
Exec targets under packages/releases/<version>/ that no longer exist and
appends a fail check with the packages/current recovery hint, downgrading an
ok overall to degraded (#114748).
This commit is contained in:
liuhao1024
2026-09-18 16:02:06 +08:00
committed by Teknium
parent 11975e61db
commit fc36a30f85
2 changed files with 181 additions and 0 deletions

View File

@@ -27,6 +27,9 @@ _ZERO_DISPLAY_MSG = "ScreenCaptureKit reachable but 0 shareable display(s) — e
_ZERO_DISPLAY_HINT = ("Wake the built-in display, connect a monitor or HDMI dummy dongle (e.g. Headless Ghost), or enable "
"a virtual display (Screen Sharing/VNC, BetterDisplay). Verify with `system_profiler SPDisplaysDataType`.")
_IO_EXC = (OSError, subprocess.TimeoutExpired)
_PRUNED_UNIT_MSG = "Exec targets {target} which no longer exists — a cua-driver upgrade pruned that release directory"
_PRUNED_UNIT_HINT = ("Point the {kind} at ~/.cua-driver/packages/current/cua-driver — `current` survives upgrades; "
"versioned packages/releases/<version>/ dirs are pruned (only the last 5 are kept)")
Report = Dict[str, Any]
_Row = Tuple[str, str, Report] # (status, message, extra {hint?, data?}) for one check
@@ -281,6 +284,57 @@ def _apply_display_count_guard(report: Report) -> Report:
report["overall"] = "degraded"
return report
def _stale_cua_exec_references(config_dir: Optional[str] = None) -> List[Tuple[str, str, str]]:
"""(kind, unit, target) for systemd user units / XDG autostart entries whose cua-driver Exec points at a
pruned ``packages/releases/<version>/`` directory.
Linux has no managed cua-driver autostart (Windows-only), so daemon units are hand-written against a concrete
release dir. The installer prunes all but the last five, so a versioned reference crash-loops with 203/EXEC
after every upgrade while every binary-level check stays green (#114748). ``packages/current`` and
still-present release dirs are healthy by construction and never reported.
"""
home = os.path.expanduser("~")
base = config_dir or os.path.join(home, ".config")
findings: List[Tuple[str, str, str]] = []
sources = (("systemd user unit", os.path.join(base, "systemd", "user"), ".service", "ExecStart"),
("XDG autostart entry", os.path.join(base, "autostart"), ".desktop", "Exec"))
for kind, directory, suffix, key in sources:
try:
names = sorted(e.name for e in os.scandir(directory) if e.name.endswith(suffix) and e.is_file())
except OSError:
continue
for name in names:
try:
with open(os.path.join(directory, name), encoding="utf-8", errors="replace") as fh:
text = fh.read()
except OSError:
continue
for line in text.splitlines():
stripped = line.strip()
tokens = stripped.split("=", 1)[1].split() if stripped.startswith(key + "=") else []
if not tokens:
continue
# first token = executable; systemd's prefix modifiers (-, +, !, :) never start a path
target = tokens[0].lstrip("-+:!")
resolved = os.path.expanduser(target.replace("%h", home))
if "/packages/releases/" in resolved and not os.path.exists(resolved):
findings.append((kind, name, target))
return findings
def _apply_stale_unit_guard(report: Report) -> Report:
"""Append a fail check per hand-written unit/autostart entry whose cua-driver Exec target was pruned — the
daemon can crash-loop (203/EXEC) for days while every binary-level check stays green (#114748). Binary-level
repair (`install`) cannot fix a stale unit reference, so doctor is the only surface that can name it."""
for kind, unit, target in _stale_cua_exec_references() if sys.platform == "linux" else ():
checks = report.get("checks")
if isinstance(checks, list):
checks.append({"name": f"daemon unit ({unit})", "status": "fail",
"message": _PRUNED_UNIT_MSG.format(target=target),
"hint": _PRUNED_UNIT_HINT.format(kind=kind)})
if report.get("overall") == "ok": # fail-worse overalls are never softened
report["overall"] = "degraded"
return report
def _wayland_environment_context(report: Report) -> Optional[Report]:
"""Linux+Wayland only: doctor probes the CLI process's environment, not the gateway's."""
if report.get("platform") != "linux" or not os.environ.get("WAYLAND_DISPLAY"):
@@ -352,6 +406,7 @@ def run_doctor(driver_cmd: Optional[str] = None, *, include: Sequence[str] = (),
print(f"cua-driver health_report failed: {e}", file=sys.stderr)
return 2
report = _apply_display_count_guard(report)
report = _apply_stale_unit_guard(report)
identity = _build_identity(binary, report)
environment = _wayland_environment_context(report)
if json_output: