Configuring macOS TCC Permissions for Accessibility and Screen Recording in pi-computer-use

To configure macOS TCC permissions for pi-computer-use, the native helper app requires both Accessibility and Screen Recording grants, which can be verified programmatically via checkPermissions and triggered through registerPermissions in src/platform/macos/permissions.ts.

The pi-computer-use repository implements a native macOS automation layer that relies on a signed helper app (/Applications/pi-computer-use.app) to perform UI observation and input. Because macOS protects these capabilities through TCC (Transparency, Consent, and Control) frameworks, the codebase includes a dedicated permission management system that guides users through granting Accessibility and Screen Recording rights while handling edge cases like code signing changes and terminal attribution.

Understanding the Permission Architecture

The permission system centers on the MacosHelperClient class defined in src/platform/macos/helper.ts. This client manages a daemonized native process that executes all macOS-specific UI operations on behalf of the JavaScript backend.

When the application launches, ensureInstalled (line 39 in helper.ts) verifies that the helper app exists in /Applications/ with proper executable permissions. Once installed, ensureDaemon launches the background process and validates protocol compatibility before any UI automation can occur.

TCC restrictions require explicit user consent for two distinct capabilities:

  • Accessibility: Allows the helper to inject input events and query UI element hierarchies
  • Screen Recording: Permits capture of display contents via ScreenCaptureKit

The status of these permissions is encapsulated in a PermissionStatus object returned by the helper, containing boolean flags for accessibility, screenRecordingCapturable (live ScreenCaptureKit probe), and screenRecordingPreflight (TCC database entry).

The Permission Flow Implementation

The coordination logic resides in src/platform/macos/permissions.ts, which implements a seven-stage workflow:

1. Pre-flight Installation Check

Before requesting permissions, the system ensures the helper binary is properly installed and codesigned. The ensureInstalled function in helper.ts handles this validation, copying the bundled helper to /Applications/ if necessary and ensuring it has the +x executable bit.

2. Permission Status Verification

The checkPermissions function (line 55 in permissions.ts) sends a "checkPermissions" command to the daemon via macosHelper.command. This returns the current authorization state without triggering user prompts, allowing the application to detect missing grants before attempting UI operations.

3. User Prompting and System Settings Integration

When permissions are missing, ensureMacosReady invokes ensurePermissions, which displays a native prompt built by permissionPrompt (line 36 in permissions.ts). The system then calls openPermissionPane to launch the appropriate System Settings section (Privacy & Security > Accessibility or Screen Recording).

4. Permission Registration

The registerPermissions function (line 82 in permissions.ts) performs the actual authorization request. This triggers two distinct macOS behaviors:

  • Displays the Accessibility consent dialog for input injection rights
  • Executes a live ScreenCaptureKit capture to pre-list the helper in the Screen Recording permissions pane

This dual-registration approach ensures the helper appears in both TCC databases immediately, rather than waiting for first-use authorization.

5. Signing Migration Handling

When the helper is re-signed (for example, during development or after certificate updates), macOS invalidates existing TCC grants. The codebase detects this scenario and emits SIGNING_MIGRATION_WARNING (line 13 in permissions.ts), advising users to toggle permissions off and on again in System Settings to re-establish the code signature relationship.

6. Attribution Edge Cases

If the helper is launched as a plain binary from a terminal rather than through the canonical app bundle, the source attribution becomes "caller" (line 17 in permissions.ts). In this mode, TCC grants attach to the launching terminal application rather than the helper itself. The permission prompt adds a specific hint warning users that grants will apply to the terminal, and recommends restarting Pi to use the proper /Applications/pi-computer-use.app bundle for correct attribution.

7. Recheck Workflow

After the user toggles permissions in System Settings, the UI presents a Recheck button. Clicking this invokes macosHelper.restart (line 30 in helper.ts), which terminates and relaunches the daemon so it picks up the fresh TCC grants without requiring a full application restart.

Code Examples for Permission Management

Checking Current Permission Status

Use this pattern to programmatically verify if the helper has sufficient rights before attempting UI automation:

import { macosHelper } from "./platform/macos/helper.ts";

async function isFullyGranted(signal?: AbortSignal): Promise<boolean> {
  const status = await macosHelper.command<{
    accessibility: boolean;
    screenRecordingCapturable: boolean;
    screenRecordingPreflight: boolean;
  }>("checkPermissions", {}, { signal });

  return status.accessibility && status.screenRecordingCapturable;
}

Source: src/platform/macos/permissions.ts, lines 55-63.

Triggering Permission Prompts

To force the macOS authorization dialogs and ScreenCaptureKit pre-registration:

import { macosHelper } from "./platform/macos/helper.ts";

async function registerAndPrompt(signal?: AbortSignal) {
  // This will show the Accessibility consent dialog and run a ScreenCaptureKit probe.
  await macosHelper.command("registerPermissions", {}, { signal, timeoutMs: 15_000 });
}

Source: src/platform/macos/permissions.ts, lines 82-86.

Rechecking After Manual Toggle

After the user updates permissions in System Settings, restart the helper to pick up changes:

import { macosHelper } from "./platform/macos/helper.ts";

async function recheckPermissions(signal?: AbortSignal) {
  await macosHelper.restart(signal);            // Restarts the helper so it picks up new grants
  const status = await macosHelper.command("checkPermissions", {}, { signal });
  console.log("Current status:", status);
}

Source: src/platform/macos/helper.ts, lines 30-37.

Resetting TCC Entries via CLI

When troubleshooting signing migration issues or corrupted permission states, reset the TCC database entries for the helper:

tccutil reset Accessibility com.injaneity.pi-computer-use
tccutil reset ScreenCapture com.injaneity.pi-computer-use

Source: docs/troubleshooting.md, lines 60-63.

Handling Edge Cases and Troubleshooting

Code Signing Changes

The helper application is codesigned with the identifier com.injaneity.pi-computer-use. When this signature changes—whether through updates, development builds, or certificate rotation—macOS considers this a new application and revokes existing TCC grants. The system detects this condition and displays the SIGNING_MIGRATION_WARNING, instructing users to manually toggle both Accessibility and Screen Recording permissions off and then on again in System Settings.

Black Screen Capture Issues

If the permission appears granted in System Settings but capture returns black frames, this typically indicates a ScreenCaptureKit entitlement mismatch. The screenRecordingPreflight boolean may return true while screenRecordingCapturable returns false. In this scenario, use the tccutil reset commands above and re-grant permissions while ensuring the helper is launched from /Applications/pi-computer-use.app rather than a terminal binary.

Terminal Attribution Conflicts

Running the helper directly from a shell (e.g., ./pi-computer-use.app/Contents/MacOS/pi-computer-use) causes TCC to attribute grants to the terminal emulator. The permissionPrompt function detects this "caller" attribution state and warns that "grants will attach to the launching app." Always use the installed app bundle in /Applications/ to ensure permissions bind correctly to the helper.

Summary

  • pi-computer-use requires a native macOS helper app (/Applications/pi-computer-use.app) that operates under TCC restrictions for Accessibility and Screen Recording.
  • The MacosHelperClient in src/platform/macos/helper.ts manages daemon lifecycle, while src/platform/macos/permissions.ts implements the authorization workflow.
  • checkPermissions queries current rights without prompting, while registerPermissions triggers the native macOS consent dialogs and ScreenCaptureKit pre-authorization.
  • Re-signing the helper invalidates existing grants; use tccutil reset commands to clear stale entries and re-grant permissions.
  • Always launch the helper from its installed app bundle location to avoid terminal attribution issues, and use macosHelper.restart to pick up permission changes without restarting the main application.

Frequently Asked Questions

Why does pi-computer-use need both Accessibility and Screen Recording permissions?

Accessibility permits the helper to query UI element hierarchies and inject keyboard/mouse events via the macOS accessibility APIs, while Screen Recording (specifically ScreenCaptureKit) allows the helper to capture the display contents for computer vision tasks. The PermissionStatus object tracks these separately because Screen Recording has two states: the TCC database entry (screenRecordingPreflight) and the actual capture capability (screenRecordingCapturable), which verifies that the helper can successfully create a capture session.

How do I fix "grant shows as granted but capture is black"?

This occurs when the TCC database records a grant for a different code signature or when the helper is running with terminal attribution. Run the CLI reset commands: tccutil reset Accessibility com.injaneity.pi-computer-use and tccutil reset ScreenCapture com.injaneity.pi-computer-use. Then ensure you are running the helper from /Applications/pi-computer-use.app rather than a terminal path, and click the Recheck button in the UI to invoke macosHelper.restart.

What is the significance of the "caller" attribution warning?

When the helper binary is launched directly from a terminal (e.g., ./pi-computer-use.app/Contents/MacOS/pi-computer-use), macOS attributes TCC grants to the launching terminal application rather than the helper itself. This causes permissions to fail when the helper runs normally. The code detects this "caller" state (line 17 in permissions.ts) and warns that grants will attach to the terminal. Resolve this by installing the helper to /Applications/ and restarting Pi to use the canonical app bundle path.

How does the Recheck button work after I toggle permissions?

Clicking Recheck triggers the macosHelper.restart method (line 30 in helper.ts), which terminates the current daemon process and spawns a new instance. This is necessary because macOS only evaluates TCC grants at process launch; the running helper cannot detect permission changes made in System Settings while it is active. The restart ensures the new process loads with the updated TCC rights, and checkPermissions is then re-executed to verify the grants.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →