> ## 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.

# Remove AI Watch

> Remove AI Watch packages, profiles, scheduled tasks, and legacy script-based installs

Use this page when decommissioning AI Watch from a fleet, rolling back a test deployment, or cleaning up legacy script-based installs. If you are only migrating from the old script-based macOS Detect rollout to the package, use [Legacy macOS Detect Cleanup](/shadow-ai/deploy/macos-cleanup-script-deployment).

## Removal order

1. Set Mode to Monitor and Sessions off from the deployment's **Manage settings** dialog while AI Watch is still installed. Also set the MDM-owned legacy `Enforcement=false` and `AutoUpdate=false` fallback values.
2. Let AI Watch reconcile to Detect-only mode so it removes Runlayer hook entries and preserves third-party hooks.
3. Verify hook configs are absent.
4. Remove the package, profiles, and local agent artifacts (the MSI uninstall removes the Windows Scheduled Tasks).
5. Restart AI clients so they stop reading any cached hook configuration.

<Warning>
  Do not remove the package first if Protect, Enforce, or Sessions installed hooks. The packaged `aiwatch` binary is what removes Runlayer hook entries safely after Mode is Monitor and Sessions is disabled.
</Warning>

<Note>
  `pkgutil --forget` is not an uninstaller. It only forgets the macOS installer receipt after files have been removed.
</Note>

## macOS

### Phase 1 - Disable hooks

In **Settings** → **MDM configuration** → **Manage settings**, set Mode to Monitor and Sessions off. Then re-push the AI Watch tenant configuration profile with automatic updates disabled and the same Monitor / Sessions fallback values for older or offline clients:

```xml theme={null}
<key>Mode</key>
<string>monitor</string>
<key>Enforcement</key>
<false/>
<key>Sessions</key>
<false/>
<key>AutoUpdate</key>
<false/>
```

Keep the AI Watch `.pkg` and tenant configuration profile assigned until hook reconciliation finishes. `AutoUpdate=false` prevents the hourly updater from racing package removal. The bootstrap LaunchDaemon then runs `aiwatch setup hooks install --mdm` on its normal schedule and removes Runlayer hook entries.

To force reconciliation immediately, push this one-time script as root:

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

/usr/local/bin/aiwatch setup hooks install --mdm
/usr/local/bin/aiwatch setup hooks check --mdm
```

`check` should exit `0` once the device is compliant with Detect-only hook state. When only Detect is enabled, stale Runlayer hooks are treated as drift so your MDM can remediate them.

### Phase 2 - Remove the package

After hook reconciliation passes, unscope the device from the AI Watch `.pkg` assignment so the package cannot be reinstalled. Keep the tenant configuration profile with `AutoUpdate=false` assigned until local cleanup finishes; then unscope all three profiles:

* Tenant configuration profile: `com.runlayer.aiwatch`
* PPPC / Full Disk Access profile: `com.runlayer.aiwatch.pppc`
* Login Items profile: `com.runlayer.aiwatch.loginitems`

While the tenant profile is still assigned, push this one-time package cleanup script as root:

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

CONSOLE_UID=$(stat -f %u /dev/console 2>/dev/null || echo "")

# Unload bundled LaunchAgents from the console user's GUI domain.
if [ -n "$CONSOLE_UID" ] && [ "$CONSOLE_UID" != "0" ]; then
    launchctl bootout "gui/${CONSOLE_UID}/com.runlayer.aiwatch" 2>/dev/null || true
    launchctl bootout "gui/${CONSOLE_UID}/com.runlayer.aiwatch.enroll" 2>/dev/null || true
fi

# Unload the hook bootstrap and update LaunchDaemons.
launchctl bootout "system/com.runlayer.aiwatch.bootstrap" 2>/dev/null || true
launchctl bootout "system/com.runlayer.aiwatch.update" 2>/dev/null || true

# Remove package-owned files and local state.
rm -f /usr/local/bin/aiwatch
rm -rf /usr/local/lib/runlayer/aiwatch
rm -f /Library/LaunchAgents/com.runlayer.aiwatch.plist
rm -f /Library/LaunchAgents/com.runlayer.aiwatch.enroll.plist
rm -f /Library/LaunchDaemons/com.runlayer.aiwatch.bootstrap.plist
rm -f /Library/LaunchDaemons/com.runlayer.aiwatch.update.plist
rm -f /Library/Google/Chrome/NativeMessagingHosts/com.runlayer.aiwatch.json
rm -rf /var/db/com.runlayer.aiwatch
rm -f /Library/Preferences/com.runlayer.aiwatch.version.plist
defaults delete com.runlayer.aiwatch.version 2>/dev/null || true

# Forget the installer receipt after files are removed.
pkgutil --forget com.runlayer.aiwatch 2>/dev/null || true

# Clear cached consent decisions for the package binary identifier.
tccutil reset All com.runlayer.aiwatch 2>/dev/null || true

echo "AI Watch package cleanup complete."
exit 0
```

## Windows

### Phase 1 - Disable hooks

In **Settings** → **MDM configuration** → **Manage settings**, set Mode to Monitor and Sessions off while AI Watch is still installed. Also update the registry fallback and disable automatic updates:

```powershell theme={null}
New-Item -Path "HKLM:\Software\Runlayer\AIWatch" -Force | Out-Null
New-ItemProperty -Path "HKLM:\Software\Runlayer\AIWatch" -Name "Mode" -Value "monitor" -PropertyType String -Force | Out-Null
New-ItemProperty -Path "HKLM:\Software\Runlayer\AIWatch" -Name "Enforcement" -Value 0 -PropertyType DWord -Force | Out-Null
New-ItemProperty -Path "HKLM:\Software\Runlayer\AIWatch" -Name "Sessions" -Value 0 -PropertyType DWord -Force | Out-Null
New-ItemProperty -Path "HKLM:\Software\Runlayer\AIWatch" -Name "AutoUpdate" -Value 0 -PropertyType DWord -Force | Out-Null
```

You can push those fallback values with an Intune script, or redeploy the app configuration with `AIWATCH_MODE=monitor AIWATCH_ENFORCEMENT=0 AIWATCH_SESSIONS=0 AIWATCH_AUTO_UPDATE=0`.

The device-local `AIWatchHooks` scheduled task runs `aiwatch.exe setup hooks install --mdm` on its next tick (at-boot + hourly); Monitor with Sessions disabled reconciles to Detect-only and removes Runlayer hook entries while preserving third-party hooks.

To force reconciliation on a device immediately, run as SYSTEM:

```powershell theme={null}
& "C:\Program Files\Runlayer\AIWatch\aiwatch.exe" setup hooks install --mdm
& "C:\Program Files\Runlayer\AIWatch\aiwatch.exe" setup hooks check --mdm
```

`check` should exit `0` once the device is compliant with Detect-only hook state. Stale Runlayer hook entries cause a non-zero exit until reconciliation removes them.

### Phase 2 - Remove the package

In Intune:

1. Remove or unassign the AI Watch app.
2. Restart AI clients on affected devices.

Use the app uninstall command:

```powershell theme={null}
msiexec /x aiwatch-<version>-win-x64.msi /qn
```

On a true uninstall the MSI's unregister custom action removes the entire hidden `\Runlayer` Task Scheduler folder (`AIWatchHooks` and `AIWatchScan`, plus any leftover legacy `AIWatchScanManager` / `AIWatchScan-<SID>` tasks), then the MSI removes `C:\Program Files\Runlayer\AIWatch\` and the managed registry values under `HKLM\Software\Runlayer\AIWatch`. There are no separate Intune Remediation assignments to clean up.

## Legacy script-based installs

For macOS fleets that used the original script-based Detect rollout, follow [Legacy macOS Detect Cleanup](/shadow-ai/deploy/macos-cleanup-script-deployment). That page removes the old `runlayer-scan` wrapper, per-user LaunchAgent, old PPPC profile, and script logs while preserving unrelated user CLI state.

For custom Windows script or hand-rolled Scheduled Task deployments, remove the MDM script or task assignment, then remove any custom task, script, or log paths your deployment created. The current Windows package registers its own tasks under the `\Runlayer` Task Scheduler folder, which the MSI uninstall removes — so this section only applies to bespoke deployments you built yourself, not the packaged MSI.

## Verification

### macOS

Before package cleanup, verify Phase 1 hook reconciliation. This proves AI Watch is in the desired Detect-only hook state; it does not prove the package has been removed.

```bash theme={null}
if /usr/local/bin/aiwatch setup hooks check --mdm; then
    echo "PASS: AI Watch hook state compliant for Detect-only cleanup phase"
else
    echo "FAIL: hook config drift remains; keep AI Watch installed and rerun reconciliation" >&2
    exit 1
fi
```

After package cleanup:

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

missing_path() {
    local path="$1"
    if [ -e "$path" ]; then
        echo "FAIL: expected removed path still exists: $path" >&2
        exit 1
    fi
    echo "PASS: removed $path"
}

missing_path /usr/local/bin/aiwatch
missing_path /usr/local/lib/runlayer/aiwatch
missing_path /Library/LaunchAgents/com.runlayer.aiwatch.plist
missing_path /Library/LaunchAgents/com.runlayer.aiwatch.enroll.plist
missing_path /Library/LaunchDaemons/com.runlayer.aiwatch.bootstrap.plist
missing_path /Library/LaunchDaemons/com.runlayer.aiwatch.update.plist
missing_path /Library/Google/Chrome/NativeMessagingHosts/com.runlayer.aiwatch.json
missing_path /Library/Preferences/com.runlayer.aiwatch.version.plist

if launchctl print system/com.runlayer.aiwatch.bootstrap >/dev/null 2>&1; then
    echo "FAIL: bootstrap daemon is still loaded" >&2
    exit 1
fi
echo "PASS: bootstrap daemon unloaded"

if launchctl print system/com.runlayer.aiwatch.update >/dev/null 2>&1; then
    echo "FAIL: update daemon is still loaded" >&2
    exit 1
fi
echo "PASS: update daemon unloaded"

if pkgutil --pkg-info com.runlayer.aiwatch >/dev/null 2>&1; then
    echo "FAIL: package receipt still present" >&2
    exit 1
fi
echo "PASS: package receipt forgotten"
```

### Windows

Before package removal, verify Phase 1 hook reconciliation. This proves AI Watch is in the desired Detect-only hook state; it does not prove the package has been removed.

```powershell theme={null}
& "C:\Program Files\Runlayer\AIWatch\aiwatch.exe" setup hooks check --mdm
if ($LASTEXITCODE -ne 0) {
  throw "Hook config drift remains; keep AI Watch installed and rerun reconciliation."
}
Write-Output "PASS: AI Watch hook state compliant for Detect-only cleanup phase"
```

After package removal:

```powershell theme={null}
$binaryPath = "C:\Program Files\Runlayer\AIWatch\aiwatch.exe"
if (Test-Path $binaryPath) {
  throw "AI Watch binary still exists at $binaryPath"
}
Write-Output "PASS: AI Watch binary removed"

$registryPath = "HKLM:\Software\Runlayer\AIWatch"
$managedValues = Get-ItemProperty -Path $registryPath `
  -Name "Host","OrgApiKey","Mode","Enforcement","Sessions","Version" `
  -ErrorAction SilentlyContinue

if ($null -ne $managedValues) {
  throw "Managed AI Watch registry values still exist at $registryPath"
}
Write-Output "PASS: managed AI Watch registry values removed"

$tasks = Get-ScheduledTask -TaskPath "\Runlayer\" -ErrorAction SilentlyContinue
if ($null -ne $tasks) {
  throw "AI Watch scheduled tasks still exist under \Runlayer\"
}
Write-Output "PASS: AI Watch scheduled tasks removed"
```
