> ## Documentation Index
> Fetch the complete documentation index at: https://docs.runlayer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Legacy macOS Detect Cleanup

> Remove the legacy script-based AI Watch Detect artifacts before rolling out the AI Watch .pkg.

## When to use this

Run this cleanup if your fleet was previously deployed with the script-based Detect path (an MDM Script + the `runlayer-scan` PPPC profile) and you are migrating to the new macOS `.pkg` flow (see the per-MDM guides linked from [Deploy AI Watch](/shadow-ai/deploy)). The two paths use different binary identifiers, paths, and PPPC pins — leaving the old artifacts in place causes:

* Duplicate scans (per-user `com.runlayer.scan` LaunchAgent + system-wide `com.runlayer.aiwatch` LaunchAgent both fire).
* Dangling TCC grants for `/usr/local/bin/runlayer-scan` that survive even after the script is unscoped.
* Confusing log output split across `/var/log/runlayer/ai_watch_detect.log` (old) and the new macOS unified log under `process == "aiwatch"`.

<Note>
  This page cleans up the legacy script-based Detect rollout. To remove the current AI Watch package itself, use [Remove AI Watch](/shadow-ai/deploy/remove).
</Note>

## What gets left behind

The script-based deployment installs and configures the following on each enrolled device:

| Artifact               | Path / Identifier                                                                                                                     |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Wrapper binary         | `/usr/local/bin/runlayer-scan` (ad-hoc signed, identifier `com.runlayer.scan`)                                                        |
| Per-user LaunchAgent   | `~/Library/LaunchAgents/com.runlayer.scan.plist`                                                                                      |
| Runlayer CLI (uv tool) | `~/.local/share/uv/tools/runlayer`                                                                                                    |
| MDM org API key        | `~/.runlayer/config.yaml` — only the `org_api_keys.ai_watch_mdm` entry                                                                |
| Logs                   | `/var/log/runlayer/ai_watch_detect.log` (fallback `/tmp/runlayer-ai_watch_detect.log`), plus `/tmp/runlayer-scan.{stdout,stderr}.log` |
| MDM-side script        | One Script per MDM (Jamf, SimpleMDM, Mosyle, Kandji, …)                                                                               |
| MDM-side PPPC profile  | `com.runlayer.scan.pppc` (pins Full Disk Access to `/usr/local/bin/runlayer-scan`)                                                    |

## Order of operations

1. Unscope the old MDM artifacts (Script + old PPPC profile) so devices stop receiving the script-based deployment.
2. Push a one-time per-device cleanup script (below) to remove leftover binaries, LaunchAgent, and logs.
3. Deploy the new `.pkg` via the guide for your MDM platform — see [Deploy AI Watch](/shadow-ai/deploy) for the index of MDM-specific guides.

Steps 1 and 2 can be staged: keep step 1 active for at least one MDM check-in cycle to let scheduled scans drain before step 2 runs.

## MDM-side cleanup

For each MDM, remove or unscope the two script-based artifacts. Names below match the artifacts created in the original setup guides.

<Tabs>
  <Tab title="Jamf Pro">
    1. **Computers → Policies** → open the "Deploy AI Watch Detect" Policy → set **Scope** to none (or delete the Policy).
    2. **Settings → Computer Management → Scripts** → delete the "AI Watch Detect" Script.
    3. **Computers → Configuration Profiles** → delete the uploaded Runlayer PPPC profile (`com.runlayer.scan.pppc`).
  </Tab>

  <Tab title="SimpleMDM">
    1. **Scripts** → delete the "AI Watch Detect" Script (or unassign it from all groups).
    2. **Profiles** → delete the uploaded Runlayer PPPC Custom Configuration Profile (`com.runlayer.scan.pppc`).
  </Tab>

  <Tab title="Mosyle">
    1. **Management → macOS → Custom Commands / Scripts** → delete the "AI Watch Detect" Script.
    2. **Management → macOS → Profiles → Custom Profile** → delete the Runlayer PPPC profile (`com.runlayer.scan.pppc`).
  </Tab>

  <Tab title="Iru/Kandji">
    1. **Library** → open the "AI Watch Detect" Custom Script → **Delete** (or remove from all Blueprints).
    2. **Library** → open the Runlayer PPPC Custom Profile (`com.runlayer.scan.pppc`) → **Delete**.
  </Tab>

  <Tab title="Workspace ONE">
    1. **Resources → Scripts** (or **Devices → Provisioning → Scripts**) → delete the "AI Watch Detect" Script (or unassign it from all Smart Groups).
    2. **Devices → Profiles** → delete the uploaded Runlayer PPPC profile (`com.runlayer.scan.pppc`).

    Note: this tab covers teardown of the **script-based** artifacts only. The new `.pkg`, the three packaged profiles (PPPC, Login Items, config), and the WS1 tenant-config profile (`com.runlayer.aiwatch.config.ws1.mobileconfig`) deployed via [Workspace ONE](/shadow-ai/deploy/workspace-one) stay in place.
  </Tab>

  <Tab title="Other MDM">
    Remove the script and the PPPC profile (`com.runlayer.scan.pppc`) using your MDM's standard "delete script" and "delete custom configuration profile" workflows. Profile identifier: `com.runlayer.scan.pppc`. Script log path: `/var/log/runlayer/ai_watch_detect.log`.
  </Tab>
</Tabs>

## Per-device cleanup script

Push the script below as a one-time **Custom Script** to every device that previously ran the script-based deployment. It is idempotent — safe to re-run, and a no-op on devices that never had the old deployment.

```bash theme={null}
#!/bin/bash
set -u

CONSOLE_USER=$(stat -f %Su /dev/console 2>/dev/null || echo "")
CONSOLE_UID=$(stat -f %u /dev/console 2>/dev/null || echo "")
USER_HOME=$(dscl . -read "/Users/$CONSOLE_USER" NFSHomeDirectory 2>/dev/null | awk '{print $2}')

if [ -n "$CONSOLE_UID" ] && [ "$CONSOLE_UID" != "0" ]; then
    /bin/launchctl bootout "gui/${CONSOLE_UID}/com.runlayer.scan" 2>/dev/null || true
fi

# Remove only the MDM org API key entry from ~/.runlayer/config.yaml.
# Other entries (e.g. a developer's personal `runlayer login` credentials)
# are preserved. Run before deleting the uv tool install below so the
# `runlayer` CLI is still available.
if [ -n "$CONSOLE_USER" ] && [ -n "$USER_HOME" ]; then
    /bin/launchctl asuser "$CONSOLE_UID" /usr/bin/sudo -u "$CONSOLE_USER" \
        "$USER_HOME/.local/bin/uvx" runlayer org-api-key remove ai_watch_mdm 2>/dev/null || true
fi

if [ -n "$USER_HOME" ]; then
    rm -f "$USER_HOME/Library/LaunchAgents/com.runlayer.scan.plist"
    rm -rf "$USER_HOME/.local/share/uv/tools/runlayer"
fi

rm -f /usr/local/bin/runlayer-scan
rm -rf /var/log/runlayer
rm -f /tmp/runlayer-ai_watch_detect.log
rm -f /tmp/runlayer-scan.stdout.log
rm -f /tmp/runlayer-scan.stderr.log

echo "Script-based AI Watch Detect cleanup complete."
exit 0
```

<Note>
  The script intentionally leaves the following in place:

  * **`uv` itself** (`/usr/local/bin/uv`, `/usr/local/bin/uvx`) — general-purpose tooling that may be in use for other workflows. Only the `runlayer` uv-tool install is removed.
  * **`~/.runlayer/config.yaml`** — only the `org_api_keys.ai_watch_mdm` entry is removed. Other host entries and any personal `runlayer login` credentials are kept. The file is **not** deleted, even if it ends up empty.
  * **OS Keychain entries** — the script-based deployment never wrote to the keychain. Any `runlayer` keychain entries belong to an interactive `runlayer login` session and are left untouched.
</Note>

## Verification

After the cleanup script runs and the new `.pkg` is deployed, confirm there is exactly one Runlayer scan agent on each device:

```bash theme={null}
# 1. Old wrapper gone
test ! -e /usr/local/bin/runlayer-scan && echo "old wrapper removed"

# 2. Old per-user LaunchAgent gone
launchctl print "gui/$(id -u)/com.runlayer.scan" 2>&1 | grep -q "Could not find service" && \
  echo "old LaunchAgent removed"

# 3. New system LaunchAgent loaded
launchctl print "gui/$(id -u)/com.runlayer.aiwatch" >/dev/null && \
  echo "new LaunchAgent loaded"

# 4. Only the new PPPC pin is active (com.runlayer.aiwatch, signed by Anysource Inc.)
sudo log show --predicate 'subsystem == "com.apple.TCC"' --last 1h | \
  grep -E "com\.runlayer\.(scan|aiwatch)"
```

A clean fleet shows TCC entries only for `com.runlayer.aiwatch`. Any remaining `com.runlayer.scan` entries indicate a device that didn't receive the cleanup script — re-target it and re-run.

## Rollback

If you need to revert to the script-based deployment temporarily, re-scope the original Script and PPPC profile in your MDM. The new `.pkg` and the old wrapper can coexist for short periods (they use distinct identifiers and paths), but scheduled scans will then run twice per cycle — use only as a short-term safety net during migration.
