# Doctor diagnostics

> Ordered --doctor ladder: pyobjc, Accessibility, Screen Recording, app installed, running, window, capture size, Vision OCR; exit codes; and the note that fresh machines may need extra prompts.

- Repository: ShawnPana/phone-harness
- GitHub: https://github.com/ShawnPana/phone-harness
- Human docs: https://grok-wiki.com/public/docs/shawnpana-phone-harness-bf80173a2a2e
- Complete Markdown: https://grok-wiki.com/public/docs/shawnpana-phone-harness-bf80173a2a2e/llms-full.txt

## Source Files

- `src/phone_harness/admin.py`
- `src/phone_harness/run.py`
- `src/phone_harness/mirror.py`
- `src/phone_harness/ocr.py`
- `install.md`

---

---
title: "Doctor diagnostics"
description: "Ordered --doctor ladder: pyobjc, Accessibility, Screen Recording, app installed, running, window, capture size, Vision OCR; exit codes; and the note that fresh machines may need extra prompts."
---

`phone-harness --doctor` (also accepted as `phone-harness doctor`) runs `run_doctor()` in `src/phone_harness/admin.py`: an ordered ladder of environment checks that prints one `[PASS]`/`[FAIL]` line per step and exits `0` only when every fatal check passes. The ladder is dependency-ordered — pyobjc frameworks, then macOS permissions, then iPhone Mirroring app state, then a live window capture and a Vision OCR probe — so the correct repair strategy is always to fix the first `FAIL` and re-run.

## Running the doctor

```bash
phone-harness --doctor
```

Each check prints a mark, a label, and — on failure — a repair hint:

```text
phone-harness doctor

  [PASS] pyobjc frameworks (Quartz, Vision, AppKit)
  [PASS] Accessibility permission (taps & keystrokes)
  [FAIL] Screen Recording permission (seeing the phone) — System Settings > Privacy & Security > Screen Recording: enable your terminal
  ...

fix the FAILs above, then re-run
```

A fully passing run ends with `all clear` instead. Both outcomes append a standing note that these are the *currently known* required permissions and that a fresh machine may prompt for more (see below).

## The check ladder

Checks run in this fixed order. Later checks depend on earlier ones — a missing window skips the capture and OCR probes entirely.

| # | Check | Implementation | Affects exit code |
|---|-------|----------------|-------------------|
| 1 | pyobjc frameworks (Quartz, Vision, AppKit) | `import Quartz, Vision, AppKit` | Yes — aborts immediately with exit `1` |
| 2 | Accessibility permission | `AXIsProcessTrusted()` from ApplicationServices | Yes |
| 3 | Screen Recording permission | `Quartz.CGPreflightScreenCaptureAccess()` | Yes |
| 4 | iPhone Mirroring installed | `Path("/System/Applications/iPhone Mirroring.app").exists()` | Yes |
| 5 | iPhone Mirroring running | `mirror.running_app()` (lookup by bundle id `com.apple.ScreenContinuity`) | No — advisory |
| 6 | Mirroring window found | `mirror.find_window()` via `CGWindowListCopyWindowInfo` | No — advisory, but gates checks 7–8 |
| 7 | Window capture works | `screencapture -x -o -l <window id>` to a temp PNG; passes when the file exceeds 20,000 bytes | Yes |
| 8 | Vision OCR works | `ocr.recognize()` on the capture; reports the recognized text-box count | Reported informationally when capture passes |

Two checks are deliberately advisory. "App running" is not fatal because iPhone Mirroring auto-launches on first use. "Window found" is not fatal either, but when it fails the hint tells you to open iPhone Mirroring once manually to pair the phone — pairing needs the physical device and cannot be done by the harness (see [Connection and session states](/connection-and-session)).

### Failure hints per step

<AccordionGroup>
<Accordion title="pyobjc frameworks">
Hint: `pip install pyobjc-framework-Quartz pyobjc-framework-Vision`, plus the original `ImportError`. This is the only check that short-circuits: without pyobjc none of the later checks can run, so the doctor returns exit code `1` immediately.
</Accordion>
<Accordion title="Accessibility permission (taps & keystrokes)">
Hint: System Settings > Privacy & Security > Accessibility — enable your terminal. Takes effect immediately once toggled.
</Accordion>
<Accordion title="Screen Recording permission (seeing the phone)">
Hint: System Settings > Privacy & Security > Screen Recording — enable your terminal. Unlike Accessibility, this grant only takes effect after the terminal app restarts.
</Accordion>
<Accordion title="iPhone Mirroring installed">
Hint: requires macOS Sequoia+ with a paired iPhone. The check looks for the app bundle at `/System/Applications/iPhone Mirroring.app`.
</Accordion>
<Accordion title="Mirroring window found">
Hint: open iPhone Mirroring once manually to pair the phone. The window lookup scans on-screen windows owned by `iPhone Mirroring` at layer 0 and ignores panels narrower than 100 points.
</Accordion>
<Accordion title="Window capture works">
Hint: capture is blank — the Screen Recording permission needs a terminal restart to take effect. A blank capture produces a tiny PNG, which is why the pass threshold is a file size above 20,000 bytes.
</Accordion>
</AccordionGroup>

## Capture and OCR probes

When a mirroring window exists, the doctor takes a real end-to-end sample rather than trusting the permission preflight alone:

1. It runs `screencapture -x -o -l <window id>` into a temporary `.png` (deleted afterward).
2. It checks the file size. A grant that has not taken effect yet still "succeeds" but produces a near-empty black frame, so the size threshold (`> 20,000` bytes) is the actual pass condition.
3. Only if the capture passes does it call `ocr.recognize(path, win)` — Apple's Vision framework at `VNRequestTextRecognitionLevelAccurate` — and print the number of recognized text boxes, e.g. `[PASS] Vision OCR works (23 text boxes)`.

This mirrors the runtime read path exactly: the same `find_window()` bounds, the same `screencapture` invocation, and the same Vision pipeline used by [Coordinates, capture, and OCR](/coordinates-capture-ocr).

## Exit codes

<ResponseField name="0" type="exit code">
All fatal checks passed. Printed footer: `all clear`. Advisory checks (app running, window found) may still show `FAIL` on a `0` exit — they do not fold into the result.
</ResponseField>

<ResponseField name="1" type="exit code">
At least one fatal check failed (pyobjc, Accessibility, Screen Recording, app installed, or capture size). Printed footer: `fix the FAILs above, then re-run`. A pyobjc import failure returns `1` immediately without running the remaining checks.
</ResponseField>

## Fresh machines may need extra prompts

The doctor prints this caveat on every run, pass or fail: Accessibility and Screen Recording are the permissions *currently known* to be required — the harness was built on an already-permissive machine, so a clean Mac may raise additional macOS approval prompts the first time an action actually runs. If `--doctor` passes but taps, typing, or capture silently do nothing, watch for a permission prompt and check System Settings > Privacy & Security for a pane asking to approve your terminal. `install.md` notes that newly identified prerequisites will be added to `--doctor` as they are pinned down.

<Tip>
Open the two known permission panes directly:

```bash
open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
open "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"
```
</Tip>

## Related pages

<CardGroup cols={2}>
<Card title="Installation" href="/installation">
Prerequisites, pyobjc install, PATH setup, and the first `phone-harness --doctor` verification run.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Recovery for blank captures after a Screen Recording grant, missing windows, and silent taps.
</Card>
<Card title="CLI reference" href="/cli-reference">
All `phone-harness` entry points, including `--doctor`, `doctor`, `skill`, and stdin script execution.
</Card>
<Card title="Connection and session states" href="/connection-and-session">
Why pairing and reconnecting the phone is a user action the doctor can only point at.
</Card>
</CardGroup>
