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

@@ -0,0 +1,126 @@
"""Linux daemon-unit references to pruned cua-driver release dirs (#114748).
Linux has no managed cua-driver autostart, so users hand-write systemd user
units / XDG autostart entries against a concrete release directory. The
installer prunes all but the last five, so a versioned Exec reference
crash-loops with 203/EXEC after every upgrade — for days, while every
binary-level check (and `computer-use install`'s binary repair) stays green.
The doctor guard surfaces the dead reference with the `packages/current`
recovery hint.
"""
import sys
import pytest
from tools.computer_use import doctor
def _write_unit(path, text):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
def test_pruned_release_reference_is_reported(tmp_path, monkeypatch):
monkeypatch.setenv("HOME", str(tmp_path / "home"))
_write_unit(
tmp_path / "systemd" / "user" / "cua-driver-screenshot.service",
"[Service]\n"
"ExecStart=%h/.cua-driver/packages/releases/0.20.0-x86_64-unknown-linux-gnu/cua-driver "
"serve --socket %h/.cache/cua-driver/cua-driver.sock\n",
)
findings = doctor._stale_cua_exec_references(str(tmp_path))
assert findings == [
(
"systemd user unit",
"cua-driver-screenshot.service",
"%h/.cua-driver/packages/releases/0.20.0-x86_64-unknown-linux-gnu/cua-driver",
)
]
def test_current_reference_and_live_release_are_silent(tmp_path):
# `packages/current` never carries a version, so it can never rot.
_write_unit(
tmp_path / "systemd" / "user" / "cua.service",
"[Service]\nExecStart=~/.cua-driver/packages/current/cua-driver serve\n",
)
# A releases/ dir that still exists on disk is healthy.
live = tmp_path / "live" / "packages" / "releases" / "0.28.2" / "cua-driver"
_write_unit(live, "")
_write_unit(tmp_path / "autostart" / "cua-driver.desktop", f"[Desktop Entry]\nExec={live}\n")
assert doctor._stale_cua_exec_references(str(tmp_path)) == []
def test_desktop_entry_dead_release_reference(tmp_path, monkeypatch):
monkeypatch.setenv("HOME", str(tmp_path / "home"))
_write_unit(
tmp_path / "autostart" / "cua-driver.desktop",
"[Desktop Entry]\nExec=~/.cua-driver/packages/releases/0.19.0/cua-driver serve\n",
)
assert [f[1] for f in doctor._stale_cua_exec_references(str(tmp_path))] == ["cua-driver.desktop"]
def test_systemd_prefix_modifier_does_not_hide_dead_target(tmp_path, monkeypatch):
monkeypatch.setenv("HOME", str(tmp_path / "home"))
# '-' means "restart failures are tolerated" — the target is still dead.
_write_unit(
tmp_path / "systemd" / "user" / "restart-anyway.service",
"[Service]\nExecStart=-%h/.cua-driver/packages/releases/0.20.0/cua-driver serve\n",
)
assert len(doctor._stale_cua_exec_references(str(tmp_path))) == 1
def test_missing_config_dirs_are_silent(tmp_path):
assert doctor._stale_cua_exec_references(str(tmp_path / "nonexistent")) == []
def test_guard_appends_fail_check_and_degrades_ok(monkeypatch):
monkeypatch.setattr(
doctor,
"_stale_cua_exec_references",
lambda: [
(
"systemd user unit",
"cua-driver-screenshot.service",
"%h/.cua-driver/packages/releases/0.20.0-x86_64-unknown-linux-gnu/cua-driver",
)
],
)
monkeypatch.setattr(sys, "platform", "linux")
out = doctor._apply_stale_unit_guard(
{"overall": "ok", "checks": [{"name": "binary_version", "status": "pass"}]}
)
assert out["overall"] == "degraded"
appended = out["checks"][-1]
assert appended["status"] == "fail"
assert "cua-driver-screenshot.service" in appended["name"]
assert "pruned" in appended["message"]
assert "packages/current" in appended["hint"]
def test_guard_never_softens_worse_overall(monkeypatch):
monkeypatch.setattr(
doctor, "_stale_cua_exec_references", lambda: [("XDG autostart entry", "cua.desktop", "/x/packages/releases/0.1.0/cua-driver")]
)
monkeypatch.setattr(sys, "platform", "linux")
out = doctor._apply_stale_unit_guard({"overall": "failed", "checks": []})
assert out["overall"] == "failed"
assert out["checks"][-1]["status"] == "fail"
def test_guard_without_checks_list_appends_nothing(monkeypatch):
monkeypatch.setattr(
doctor, "_stale_cua_exec_references", lambda: [("systemd user unit", "cua.service", "/x/packages/releases/0.1.0/cua-driver")]
)
monkeypatch.setattr(sys, "platform", "linux")
# No checks list to append to → no unexplained degraded either.
out = doctor._apply_stale_unit_guard({"overall": "ok"})
assert out == {"overall": "ok"}
def test_guard_off_linux_never_scans(monkeypatch):
monkeypatch.setattr(doctor, "_stale_cua_exec_references", lambda: pytest.fail("must not scan off Linux"))
monkeypatch.setattr(sys, "platform", "darwin")
report = {"overall": "ok", "checks": []}
assert doctor._apply_stale_unit_guard(report) is report

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: