auspex: Retiring Span coding-hooks on Windows

Last updated: August 27, 2026

Span's earlier agent, coding-hooks, and its replacement, auspex, both register hooks in the same coding tools. While both are installed, every event is captured twice — once into each product's pipeline. This guide gives you a check-then-uninstall script to remove coding-hooks automatically, but only on devices where auspex has fully taken over.

Do not blanket-uninstall coding-hooks across the fleet. A device where the auspex rollout has not landed — or where it landed but never received its organization credentials — would stop reporting entirely. That is why the script verifies auspex first and exits without doing anything when the checks fail.

What "safe to retire" means

The script removes coding-hooks only when all of the following hold:

  1. auspex is installed at the machine-wide path (C:\Program Files\auspex\bin\auspex.exe), i.e. deployed by your MDM rather than self-installed.
  2. auspex reports the managed install mode — it resolved the managed configuration your MDM placed, so it is genuinely under central management.
  3. The daemon is running and healthyauspex status exits 0, meaning the agent is alive and its own checks pass.
  4. Identity is provisioned — both the organization token and the work email resolve. Without them the device captures locally but never uploads, so retiring the old agent would create a silent gap.

Failing any check, the script exits 0 without changing anything, so it is safe to run on a schedule across a mixed fleet.

Why both check 3 and check 4: auspex status exits 0 for a healthy or merely degraded agent and non-zero only when a check is genuinely failing (or the daemon is unreachable). A device that is running fine but has never received its organization token reports a warning, not a failure — so it would pass check 3 on its own. Check 4 is what catches it.

The script

Save as Retire-CodingHooks.ps1 and run it as SYSTEM / administrator (uninstalling a per-machine product requires elevation).

#Requires -Version 5.1

# NOT 'Stop'. `auspex status` prints device facts (install mode, warnings) to STDERR by design, so
# stdout stays clean for --json. Under 'Stop', PowerShell promotes any native-command stderr into a
# terminating NativeCommandError and the script dies on a command that succeeded. Every failure path
# below is checked explicitly, so implicit error handling buys nothing here.
$ErrorActionPreference = 'Continue'

# No Set-StrictMode either: the uninstall hives hold keys with no DisplayName at all, and strict mode
# turns each missing-property read into an error record. The property-existence filter is the guard.

$Auspex = 'C:\Program Files\auspex\bin\auspex.exe'

function Write-Log($m) { Write-Output ("[{0}] {1}" -f (Get-Date -Format s), $m) }

# 1. auspex present at the machine-wide path?
if (-not (Test-Path $Auspex)) { Write-Log 'auspex not installed (machine-wide) — leaving coding-hooks in place.'; exit 0 }

# 2 + 3. Healthy, and under central management?
# ToString() each record: 2>&1 turns stderr lines into ErrorRecords, which render as multi-line
# PowerShell error blocks if left to format themselves. We only want the text.
$status  = ((& $Auspex status 2>&1 | ForEach-Object { $_.ToString() }) -join "`n")
$healthy = ($LASTEXITCODE -eq 0)
$managed = $status -match 'install mode:\s*managed'
if (-not $healthy) { Write-Log "auspex status is not healthy — leaving coding-hooks in place. Output: $status"; exit 0 }
if (-not $managed) { Write-Log 'auspex is not in managed install mode — leaving coding-hooks in place.'; exit 0 }

# 4. Identity provisioned (token AND work email both resolve)?
$auth     = ((& $Auspex auth show 2>&1 | ForEach-Object { $_.ToString() }) -join "`n")
$hasToken = $auth -match 'token:\s*set'
$hasEmail = $auth -match 'work_email:\s*\S+@\S+'
if (-not ($hasToken -and $hasEmail)) { Write-Log 'auspex has no organization identity yet — leaving coding-hooks in place.'; exit 0 }

Write-Log 'auspex is installed, managed, healthy and provisioned — retiring coding-hooks.'

# Uninstall coding-hooks via its registered uninstaller (per-machine and per-user hives).
$roots = @(
  'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*',
  'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*',
  'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
$found = $false
foreach ($entry in Get-ItemProperty $roots -ErrorAction SilentlyContinue |
                   Where-Object DisplayName |
                   Where-Object { $_.DisplayName -match '^Span\b.*\bHooks\b|coding.?hooks' }) {
  $found = $true
  Write-Log ("Uninstalling '{0}' {1}" -f $entry.DisplayName, $entry.DisplayVersion)
  if ($entry.PSChildName -match '^\{[0-9A-Fa-f-]{36}\}$') {
    Start-Process msiexec.exe -ArgumentList '/x', $entry.PSChildName, '/qn', '/norestart' -Wait
  } elseif ($entry.QuietUninstallString) {
    Start-Process cmd.exe -ArgumentList '/c', $entry.QuietUninstallString -Wait
  } elseif ($entry.UninstallString) {
    Start-Process cmd.exe -ArgumentList '/c', "$($entry.UninstallString) /S" -Wait
  }
}
if (-not $found) { Write-Log 'No coding-hooks installation found — nothing to remove.' }

Write-Log 'Done.'
exit 0

Adjust before you deploy

  • DisplayName match. coding-hooks registers under the product name Span IDE Hooks (not "coding-hooks" — the pattern ^Span\b.*\bHooks\b is what matches it). auspex itself registers as auspex and contains no "Hooks", so it is never matched. Confirm what your own fleet reports before rolling out, since older builds may differ:

    Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*,
                     HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*,
                     HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\* -ErrorAction SilentlyContinue |
      Where-Object DisplayName | Where-Object { $_.DisplayName -match 'span|hook' } |
      Select-Object DisplayName, DisplayVersion, PSChildName
    
  • Per-user installs. If coding-hooks was installed per-user, the HKCU entries only exist in that user's hive — a SYSTEM-context run won't see them. Either run the script in the user context as well, or remove the per-user copy in the same pass as your other user-scope cleanup.

Running it on a schedule

Run it periodically, not once: devices get auspex at different times, and a device that fails the checks today should be retried later.

  • Intune — add as a Platform script (Devices → Scripts and remediations → Platform scripts), run in system context, 64-bit PowerShell. Intune re-runs a failed script but not a successful one, so for recurring evaluation use a remediation instead: the detection script runs the four checks and reports "remediation needed" only when they all pass, and the remediation script does the uninstall.
  • Workspace ONE — a Freestyle workflow or a Product with a script step, scheduled daily.
  • Rippling — a scheduled device script in the Windows script library.

Whatever the mechanism, the safety property is the same: on a device that is not ready, the script logs why and exits 0.

Verifying afterwards

On a retired device, auspex should be the only agent registering hooks:

& 'C:\Program Files\auspex\bin\auspex.exe' status
Get-Content "$env:APPDATA\Code\User\settings.json" | Select-String 'hook'
Get-Content "$env:USERPROFILE\.claude\settings.json" | Select-String 'hook'

You should see auspex's entries and no coding-hooks entries. If both appear for a while, that is expected until the user's tools restart — but sustained double entries mean the uninstall did not complete; capture the script's log output and send it to the Span team.