macOS permissions
The TCC permissions Zanei requests, how to grant them, and how zanei doctor verifies the recorder's state.
Depending on capture.sources and the two content opt-ins, Zanei uses up to three macOS permissions (TCC). Screen recording is not one of them.
Required permissions
| Permission | System Settings item | Used for | Related event types |
|---|---|---|---|
| Accessibility | Accessibility | The shared frontmost-window identity, window titles, focus, UI elements, visible content snapshots | window.* ui.* input.* clipboard.* browser.navigate content.snapshot |
| Input Monitoring | Input Monitoring | Detecting keys, scrolling, clicks | input.* ui.click clipboard.* |
| Automation | Automation (per target app) | Reading Chrome/Safari URL/window eligibility for URL capture and content suppression | browser.navigate; browser ui.value/input.key/clipboard bodies and content.snapshot |
Some properties worth knowing:
- App launch, termination, and switching (
app.*) require no permissions. With nothing granted, Zanei still records app-level activity. inputandbrowseruse Accessibility for the single frontmost-window identity that binds their events to an app and window; their event detection still uses Input Monitoring and Automation respectively.- A permission counts as required only when a feature that needs it is enabled.
capture.content_snapshotis independent fromcapture.sources: it requires Accessibility even whenwindowis absent. Snapshot capture by itself does not require Input Monitoring. - Without
browser, Automation for a supported browser is still required when that browser is allowed by the text-content scope withtext_contentanduiorinput, or by an enabled content-snapshot scope. A browser excluded from all relevant scopes does not require Automation.
First-time setup
The signed app bundle has the stable TCC identity dev.zanei.recorder. Permission dialogs identify it as Zanei. Accessibility adds a Zanei row automatically, but Input Monitoring may omit the row even after the dialog grant has taken effect. Use this order:
- Run
zanei start. On a fresh setup, the normal dialog order is Accessibility → grant → Input Monitoring. The recorder waits up to two minutes for Accessibility to be granted and does not request Input Monitoring during that wait. After Accessibility is granted, or when the two-minute limit is reached, it requests Input Monitoring; the limit exists so that users who decline Accessibility still get a chance to grant Input Monitoring. macOS may show the Input Monitoring dialog next, but on many systems it does not. If it does not appear, continue to step 3 and usezanei doctor --fixto add the app manually. Each permission is requested only once during that startup. Ifstartexits with code 3, the daemon has started and its heartbeat reported the missing permissions; it remains running. While a first-run permission dialog is awaiting a response,zanei doctormay report Automation asnot_determined; respond to the dialog, then run it again. - Open the matching pane below. Switch the automatically added Accessibility row ON. If Input Monitoring lists
Zanei, switch that row ON; absence of that row alone does not mean the dialog grant failed. - To manage an omitted Input Monitoring permission from the list, click
+and select the installedZanei.app. While the permission is still reported missing,zanei doctor --fixopens the pane, copies the exact resolvedZanei.approot, and reveals the same app in Finder. Press Command-Shift-G in the file dialog and paste that path. A manually added bundle entry persists. - Run
zanei stop && zanei startso the recorder picks up the grants. - Run
zanei doctorto verify the state enforced for the recorder itself. Its recorder-reported result is authoritative even when Input Monitoring has no row.
| Permission | Location |
|---|---|
| Accessibility | Privacy & Security → Accessibility |
| Input Monitoring | Privacy & Security → Input Monitoring |
| Automation | Privacy & Security → Automation (macOS asks on the first Apple Event; Zanei does not request it at startup) |
Granting is always your own action: zanei doctor --fix opens the required panes and explains the relevant controls. Its exact behavior is described below.
Verify with doctor
zanei doctor
While a recorder heartbeat is fresh, doctor reports that recorder’s own permission state. Otherwise it probes from the doctor process and adds a human-readable note that the recorder must be started to see its own permissions. If anything is missing, it explains what to grant and exits with code 3.
zanei doctor --fix
--fix opens missing required-permission panes one at a time. When more than one pane is needed,
it waits for Return between panes and does not wait after the final pane. --json --fix is accepted:
Zanei prints the JSON report first, then runs the same interactive guide when a required permission
is missing, so stdout is not JSON-only. When Accessibility or Input Monitoring is missing, the
guide copies the symlink-resolved Zanei.app root, or the executable for an unbundled installation,
and reveals that same target in Finder. For Automation, it directs you to the app/executable
running the recorder and its Google Chrome or Safari toggle. This requester may differ from
the diagnostic executable. Automation alone does not copy a path or reveal a Finder target,
and restarting does not promise another permission dialog after denial. When nothing required
is missing, --json --fix prints only JSON.
For agents and scripts: --json
zanei doctor --json
{
"ok": false,
"reported_by_recorder": true,
"capture_sources": ["app", "window", "ui", "input", "browser"],
"capabilities": {
"read_accessibility_tree": {
"state": "available", "required": true,
"required_for": ["window.focus", "window.title", "ui.focus", "ui.click", "ui.value", "content.snapshot"],
"detail": { "platform": "macos", "permission": "accessibility", "status": "granted", "settings_url": "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" }
},
"observe_input": {
"state": "action_required", "required": true,
"required_for": ["input.key", "input.scroll", "clipboard.copy", "clipboard.paste", "ui.click"],
"detail": { "platform": "macos", "permission": "input_monitoring", "status": "denied", "settings_url": "x-apple.systempreferences:com.apple.preference.security?Privacy_ListenEvent" }
},
"automate_browser": {
"state": "deferred", "required": true,
"detail": { "platform": "macos", "permission": "automation", "status": "not_determined", "settings_url": "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation", "target_bundle_id": "com.google.Chrome" }
}
}
}
Capability state is available, action_required, or deferred. The nested macOS detail carries the native permission name and status, System Settings URL, and optional target bundle ID. reported_by_recorder distinguishes a recorder heartbeat from a local-process probe. Missing permissions use the dedicated exit code 3, so agents and scripts can detect a permission problem without parsing output (see exit codes).
Remove or reset permissions
If a pane contains a Zanei row, delete it to remove that permission. Alternatively, stop Zanei and run the service-specific reset for each decision you want to clear before starting it again:
zanei stop
tccutil reset Accessibility dev.zanei.recorder
tccutil reset ListenEvent dev.zanei.recorder
zanei start
Run these bundle-ID resets while Zanei is still installed. tccutil can resolve only a bundle registered with LaunchServices, so the commands cannot address dev.zanei.recorder after uninstalling it. They also do not address a raw, unbundled cargo install executable.
Permissions and code signing
macOS ties permissions to the signed app identity. Release artifacts contain the signed, notarized, and stapled Zanei.app; the Homebrew formula installs that same app under libexec and exposes a CLI symlink. Both execute Zanei.app/Contents/MacOS/zanei, so TCC attributes access to dev.zanei.recorder across upgrades.
Scope
- Zanei asks macOS to present Accessibility and Input Monitoring decisions, but it never grants permissions or touches the TCC database. The reset commands above are explicit user actions. Automation remains tied to the first real Apple Event.
- Each recorder requests required missing permissions only once at startup. It does not repeatedly prompt during the same run.
- A background
startwith missing permissions exits with code 3 after the recorder reports them. The daemon remains running in a degraded state; after granting, restart it withzanei stop && zanei start.