fix(computer-use): flag macOS zero-display capture in doctor + discovery reason

A headless Mac or asleep built-in panel leaves ScreenCaptureKit with 0
shareable displays while TCC grants pass — health_report stays ok and
every capture silently returns 0x0 (#67165). Guard at the report seam
(_apply_display_count_guard, both real and fallback paths): flips the
screen_capture_capability check to fail with recovery actions (wake
display / HDMI dummy / virtual display) and downgrades ok -> degraded.
The empty-discovery reason ladder gains the matching darwin rung.

Composed from #52949 (sujeet111) and #67259 (webtecnica); both PRs
predate the doctor rewrite and the envelope normalization on main, so
this reimplements their shared intent at the current seams.

Co-authored-by: Sujeet <64351924+sujeet111@users.noreply.github.com>
Co-authored-by: webtecnica <75556242+webtecnica@users.noreply.github.com>
This commit is contained in:
Teknium
2026-08-15 17:23:45 -07:00
parent d303e18d09
commit 460d345642
3 changed files with 111 additions and 2 deletions
@@ -0,0 +1,60 @@
"""macOS ScreenCaptureKit display_count=0 diagnosability.
Composed from PR #52949 (sujeet111, doctor guard) and PR #67259
(webtecnica, actionable-hint direction): a headless Mac or asleep panel
leaves SCK with zero shareable displays while TCC grants pass, so
health_report says ok and every capture returns 0x0 silently. The guard
turns that into a failed check + degraded overall + recovery hint.
"""
from tools.computer_use.doctor import _apply_display_count_guard
def _report(status="pass", count=0, overall="ok", name="screen_capture_capability"):
return {
"overall": overall,
"checks": [
{
"name": name,
"status": status,
"message": "ScreenCaptureKit reachable",
"data": {"display_count": count},
}
],
}
def test_zero_displays_downgrades_ok_to_degraded():
out = _apply_display_count_guard(_report(count=0))
assert out["overall"] == "degraded"
assert out["checks"][0]["status"] == "fail"
assert "0 shareable" in out["checks"][0]["message"]
assert "dongle" in out["checks"][0]["hint"]
def test_positive_display_count_untouched():
out = _apply_display_count_guard(_report(count=1))
assert out["overall"] == "ok"
assert out["checks"][0]["status"] == "pass"
def test_already_failed_check_not_rewritten():
out = _apply_display_count_guard(_report(status="fail", count=0, overall="degraded"))
# Guard only flips pass->fail; an existing failure keeps its message.
assert out["checks"][0]["message"] == "ScreenCaptureKit reachable"
def test_missing_data_and_other_checks_ignored():
out = _apply_display_count_guard(
{"overall": "ok", "checks": [{"name": "binary_version", "status": "pass"}]}
)
assert out["overall"] == "ok"
out2 = _apply_display_count_guard({"overall": "ok", "checks": "bogus"})
assert out2["overall"] == "ok"
def test_overall_failed_not_upgraded():
out = _apply_display_count_guard(_report(count=0, overall="failed"))
# fail-worse states are never softened to degraded.
assert out["overall"] == "failed"
assert out["checks"][0]["status"] == "fail"
+9
View File
@@ -357,6 +357,15 @@ def _empty_discovery_reason() -> str:
)
if sys.platform == "linux" and not os.environ.get("DISPLAY"):
return "no DISPLAY is set — X11/XWayland is not reachable from this process"
if sys.platform == "darwin":
# Headless Mac / asleep panel: ScreenCaptureKit has 0 shareable
# displays while TCC grants look fine (#67165, #52925 lineage).
return (
"window discovery returned no windows; on macOS this usually "
"means no shareable display (headless Mac or panel asleep) — "
"wake the display or attach a monitor/HDMI dummy, then run "
"`hermes computer-use doctor`"
)
return (
"window discovery returned no windows; run `hermes computer-use "
"doctor` (display reachability, AX capability)"
+42 -2
View File
@@ -693,13 +693,53 @@ def _drive_health_report_or_fallback(
) -> Dict[str, Any]:
"""Prefer real health_report; on denial/non-schema, synthesize via probes."""
try:
return _drive_health_report(
report = _drive_health_report(
binary, include=include, skip=skip, timeout=timeout,
)
except HealthReportUnavailable as e:
return _compose_fallback_report(
report = _compose_fallback_report(
binary, reason=str(e), timeout=timeout,
)
return _apply_display_count_guard(report)
def _apply_display_count_guard(report: Dict[str, Any]) -> Dict[str, Any]:
"""Downgrade an 'ok' report whose screen capture has zero displays.
macOS ScreenCaptureKit reports ``display_count=0`` on headless Macs and
when the built-in panel is asleep — TCC grants are fine, health_report
can still say pass/ok, but every capture will come back 0x0. Marking
the check failed (with the recovery actions) turns an undiagnosable
silent failure into an actionable one. Applied at the report seam so
the real health_report path and the composed fallback both get it.
Composed from PR #52949 (sujeet111) and PR #67259 (webtecnica).
"""
checks = report.get("checks")
if not isinstance(checks, list):
return report
for check in checks:
if not isinstance(check, dict):
continue
if check.get("name") != "screen_capture_capability":
continue
data = check.get("data")
count = data.get("display_count") if isinstance(data, dict) else None
if count == 0 and check.get("status") == "pass":
check["status"] = "fail"
check["message"] = (
"ScreenCaptureKit reachable but 0 shareable display(s) — "
"every capture will return 0x0."
)
check["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`."
)
if report.get("overall") == "ok":
report["overall"] = "degraded"
return report
def _print_text_report(