# How OpenLogi Handles macOS TCC Access: Architecture and Implementation

> Discover how OpenLogi manages macOS TCC access with its agent process architecture. Learn about persistent grants and non-intrusive permission queries.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: architecture
- Published: 2026-09-12

---

**OpenLogi isolates privileged macOS capabilities in a separate agent process that becomes the TCC-responsible entity, allowing persistent grants independent of the GUI lifecycle while providing non-intrusive permission status queries.**

OpenLogi, an open-source automation framework maintained by AprilNEA, implements a strict macOS TCC (Transparency, Consent, and Control) access model to comply with Apple’s privacy sandbox requirements. The architecture deliberately separates the graphical interface from privileged system operations to ensure that accessibility and input monitoring grants survive application restarts. This article examines the source code to explain how OpenLogi manages macOS TCC access through process isolation, launch-time orchestration, and programmatic permission handling.

## TCC-Responsible Process Architecture

macOS ties TCC grants to the **code signature** of the process that first requests the permission, meaning the entity that triggers the consent dialog owns the grant permanently. OpenLogi ensures that the **agent binary** (`openlogi-agent`)—not the GUI (`openlogi-desktop`)—becomes the TCC-responsible process for Accessibility and Input Monitoring.

### Agent Launch via LaunchServices

In [`crates/openlogi-desktop/src/services/ipc/launch.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-desktop/src/services/ipc/launch.rs), the `spawn_agent` function (lines 36-44) explicitly launches the agent via **LaunchServices** using the command `open -g -n <path>`. This creates a distinct process with its own bundle identity and code signature, ensuring the agent "is its own TCC responsible process." For development builds, the system falls back to a direct `exec` call, which deliberately disclaims the GUI’s TCC identity to prevent the agent from inheriting the parent’s permission context.

### Why the GUI Cannot Own the Grant

If the GUI owned the Accessibility grant, macOS would revoke the permission when the GUI process exits, immediately breaking the persistent input hook required for automation. By isolating the hook in the agent and launching it as a separate bundle, the grant remains valid for the lifetime of the **agent process**, independent of the GUI’s process lifecycle. This separation is critical for maintaining continuous input monitoring across user sessions.

## Querying Permission Status Without Prompting

OpenLogi reads current TCC statuses through non-prompting APIs defined in [`crates/openlogi-permissions/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-permissions/src/macos.rs). The module exposes specific functions for each privileged capability, returning a `PermissionStatus` enum (`Granted`, `Denied`, or `Unknown`) without triggering system dialogs.

- **`input_monitoring()`**: Uses `IOHIDCheckAccess(IOHIDRequestType::ListenEvent)` to query Input Monitoring status.
- **`bluetooth()`**: Invokes `CBManager.authorization` via the Objective-C runtime to check Bluetooth authorization.
- **`camera()`**: Delegates to `openlogi-camera::camera_authorization()` for Camera access status.
- **`open_pane(Permission)`**: Opens the appropriate System Settings privacy pane using the `x-apple.systempreferences` URL scheme (lines 68-76) when the user needs to modify a grant manually.

The URL mapping specifically anchors to `Privacy_Accessibility`, `Privacy_ListenEvent`, `Privacy_Bluetooth`, or `Privacy_Camera` depending on the permission type.

## Launch-Time TCC Handling and Launchd Integration

When the GUI detects that the agent socket is missing, it attempts to **kick-start** the registered launchd service through `kickstart_registered_agent` (lines 15-52 in [`launch.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/launch.rs)). If that fails, the system calls `ensure_registered` to register the agent as a login item on-demand, then retries the kick-start.

Both paths use the system-wide launchd domain `gui/<uid>/…`, which automatically confers a unique TCC identity upon the launched agent. Production builds rely on this supervised service model to guarantee correct TCC attribution, while development profiles fall back to direct `open` calls that bypass launchd registration but still maintain process isolation.

## Code Examples

The following patterns demonstrate how OpenLogi interacts with the TCC subsystem programmatically:

```rust
// Query Input Monitoring status without prompting
use openlogi_permissions::macos::{input_monitoring, PermissionStatus};

match input_monitoring() {
    PermissionStatus::Granted => println!("Input Monitoring active"),
    PermissionStatus::Denied => eprintln!("Permission denied"),
    PermissionStatus::Unknown => println!("Status undetermined"),
}

```

```rust
// Direct user to the Accessibility permission pane
use openlogi_permissions::{Permission, open_pane};
open_pane(Permission::Accessibility);

```

```rust
// Launch agent with independent TCC identity (macOS)
fn launch_agent(path: &std::path::Path) -> std::io::Result<()> {
    std::process::Command::new("/usr/bin/open")
        .args(&["-g", "-n"])
        .arg(path)  // Path to .app bundle
        .spawn()?;
    Ok(())
}

```

## Summary

- **Process Isolation**: The `openlogi-agent` binary is launched via LaunchServices or direct `exec` to establish its own code signature as the TCC-responsible entity, separate from the GUI.
- **Non-Intrusive Queries**: Permission status is checked via native macOS APIs (`IOHIDCheckAccess`, `CBManager.authorization`) without triggering consent dialogs.
- **System Settings Integration**: When user intervention is required, OpenLogi opens the specific privacy pane using `x-apple.systempreferences` URL schemes.
- **Launchd Orchestration**: Production builds use launchd’s supervised service domain (`gui/<uid>/…`) to ensure persistent TCC identity across restarts, while dev builds use fallback execution paths.

## Frequently Asked Questions

### Why does OpenLogi use a separate agent process for TCC instead of the main GUI?

The agent process maintains persistent TCC grants independent of the GUI lifecycle. If the GUI owned the Accessibility grant, macOS would revoke the permission when the GUI exits, breaking automation hooks. The separate process ensures that input monitoring remains active even when the user interface restarts.

### How does OpenLogi check if Input Monitoring permission is granted without triggering a system prompt?

The system calls `IOHIDCheckAccess(IOHIDRequestType::ListenEvent)` through the `input_monitoring()` function in [`crates/openlogi-permissions/src/macos.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/crates/openlogi-permissions/src/macos.rs). This API returns the current authorization status silently without presenting a consent dialog to the user.

### What happens if the agent fails to launch via launchd in a production build?

If `kickstart_registered_agent` fails, OpenLogi invokes `ensure_registered` to register the agent as a login item and then attempts to kick-start again. This on-demand registration ensures that the agent eventually launches with the correct launchd context and TCC identity, even if the initial service lookup fails.

### How does OpenLogi ensure TCC grants persist after application updates?

The project uses code signing utilities defined in [`xtask/src/commands/macos/bundle/signing.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/commands/macos/bundle/signing.rs) and [`xtask/src/commands/macos/bundle/identity.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/commands/macos/bundle/identity.rs) to maintain consistent bundle identifiers and entitlements across releases. Since macOS TCC grants are tied to the **code signature**, consistent signing ensures that updates do not invalidate existing user consents.