Offboarding
When an Arrakis tenant is disconnected, customer endpoints must stop sending telemetry to the Arrakis ingest and any Arrakis-installed bytes must be removed without disturbing customer-owned configuration files (Cursor’s hooks.json, base managed-settings.json, customer launchd plists). This page documents the prescribed sequence, per-tool cleanup, and verification.
Offboarding sequence
- Arrakis-initiated profile withdrawal. Arrakis publishes an empty / withdrawn version of every Arrakis-owned configuration profile (Claude Code
managed-settings.dfragments, Cursor enterprisehooks.jsonentries, Codex wrapper env block) to the customer MDM via the live-policy-push API. The customer can verify the change in the Arrakis console under Settings → Integrations → MDM → Offboarding. - Endpoints sync. Devices receive the withdrawn payload at next MDM check-in. If force-sync was opted in (see MDM Sync → Live policy push), Arrakis triggers immediate sync; otherwise wait for the per-MDM cadence (Jamf 15 min, Intune ~8 hours, Iru (formerly Kandji) ~15 min — see MDM Sync → Per-MDM sync cadence).
- Customer revokes Arrakis credentials. After endpoints have synced, the customer revokes the Arrakis MDM API credential and (if separately issued) the Anthropic / OpenAI Admin API keys.
- Verification. Customer runs the per-tool verification probes in this page to confirm no further telemetry leaves any endpoint.
If your fleet did not have force-sync opted in, allow at least one full MDM check-in cycle before proceeding to step 3. Revoking credentials before endpoints have synced strands the in-flight withdrawal.
Per-tool endpoint cleanup
Claude Code
What was installed (Arrakis-controlled paths)
| OS | Arrakis-controlled paths |
|---|---|
| macOS | /Library/Application Support/ClaudeCode/managed-settings.json; …/managed-settings.d/<arrakis-managed-fragment>; managed-prefs plist at /Library/Managed Preferences/<user>/com.anthropic.claudecode.plist |
| Windows | C:\Program Files\ClaudeCode\managed-settings.json; …\managed-settings.d\<arrakis-managed-fragment>; HKLM\SOFTWARE\Policies\ClaudeCode\ManagedSettings (REG_SZ JSON) |
| Linux / WSL | /etc/claude-code/managed-settings.json; /etc/claude-code/managed-settings.d/<arrakis-managed-fragment> |
The Arrakis platform provides the exact fragment file name used in your tenant; copy it from Settings → Integrations → Deployment → Offboarding.
What we must not touch
User state lives outside the Arrakis-owned paths above. Cleanup MUST preserve every file under:
~/.claude/,~/.claude/settings.json,~/.claude/CLAUDE.md,~/.claude/agents/- Project
.claude/settings.json,.claude/settings.local.json,.claude/CLAUDE.md,CLAUDE.local.md - Any
.mcp.json - Windows
%USERPROFILE%\.claude\…
These are developer-owned. The cleanup script must not delete or modify them. (Cite: https://code.claude.com/docs/en/settings — Settings file locations.)
Removal commands
Substitute <arrakis-managed-fragment> with the fragment file name listed in the Arrakis platform under Settings → Integrations → Deployment → Offboarding.
macOS (bash):
sudo rm -f "/Library/Application Support/ClaudeCode/managed-settings.d/<arrakis-managed-fragment>"
# Only if managed-settings.json was Arrakis-only on this fleet:
sudo rm -f "/Library/Application Support/ClaudeCode/managed-settings.json"
sudo profiles list | grep -i claudecodeVerification cue: the profiles list grep should return no Arrakis-installed payload identifiers.
Windows (PowerShell, admin):
Remove-Item "C:\Program Files\ClaudeCode\managed-settings.d\<arrakis-managed-fragment>" -Force -ErrorAction SilentlyContinue
Remove-ItemProperty -Path "HKLM:\SOFTWARE\Policies\ClaudeCode" -Name "ManagedSettings" -ErrorAction SilentlyContinue
Remove-Item -Path "HKLM:\SOFTWARE\Policies\ClaudeCode" -Force -ErrorAction SilentlyContinueVerification cue: Get-Item HKLM:\SOFTWARE\Policies\ClaudeCode must report the key does not exist.
Linux / WSL:
sudo rm -f /etc/claude-code/managed-settings.d/<arrakis-managed-fragment>
sudo rm -f /etc/claude-code/managed-settings.json # only if Arrakis-onlyVerification cue: ls /etc/claude-code/managed-settings.d/ shows no Arrakis fragment.
Verification (Claude Code)
claude /doctor— managed-settings provenance section must not list Arrakis paths.claude --debugthen run a one-shot — the debug log must not contain “OTel exporter started” lines pointing at the Arrakis hostname.- Fresh shell env check:
env | grep -E 'CLAUDE_CODE_ENABLE_TELEMETRY|OTEL_'(POSIX) orGet-ChildItem Env: | Where-Object Name -match '^(CLAUDE_CODE|OTEL_)'(PowerShell) — empty. - macOS additional:
defaults read /Library/Managed\ Preferences/com.anthropic.claudecode 2>/dev/nullshould fail with no such file.
Cursor hook
The shared-file constraint
hooks.json is strict JSON (no comments, no JSONC). Customers may have other hooks in this file that pre-existed our installation, so cleanup CANNOT delete the file or replace it wholesale. Removal must surgically remove only Arrakis-owned entries.
(Cite: https://cursor.com/docs/agent/hooks )
Where hooks.json lives
| Scope | Path |
|---|---|
| Enterprise / system-wide | macOS: /Library/Application Support/Cursor/hooks.json · Linux/WSL: /etc/cursor/hooks.json · Windows: C:\ProgramData\Cursor\hooks.json |
| User | ~/.cursor/hooks.json |
| Project | <project-root>/.cursor/hooks.json |
The Arrakis hook ships at the Enterprise path via MDM. Cleanup focuses on that path; the User and Project paths are developer-owned and must not be touched.
Identifying Arrakis entries
Every Arrakis-installed hook entry carries TWO identifiers (see OTel Collector & Arrakis Endpoint → Arrakis namespace convention):
- The
commandpath is rooted at an Arrakis-owned absolute prefix. - A namespaced metadata field on the entry marks the entry as Arrakis-owned (with owner, id, and version sub-fields).
Removal matches on either identifier so cleanup is robust against hand-edits between install and uninstall.
The exact Arrakis-owned path prefix and the metadata field identifier are provisioned per-tenant; copy them from the Arrakis platform under Settings → Integrations → Deployment → Offboarding.
Removal commands
Substitute <arrakis-hook-install-dir> and <arrakis-meta-field> with the values listed in the Arrakis platform.
macOS / Linux (jq):
HOOKS=/Library/Application\ Support/Cursor/hooks.json # Linux: /etc/cursor/hooks.json
sudo cp "$HOOKS" "$HOOKS.bak.$(date +%s)"
sudo jq --arg prefix "<arrakis-hook-install-dir>" --arg meta "<arrakis-meta-field>" '
.hooks |= with_entries(
.value |= map(select(
(.[$meta].owner // "") != "arrakis"
and ((.command // "") | startswith($prefix) | not)
))
)
| .hooks |= with_entries(select(.value | length > 0))
' "$HOOKS" | sudo tee "$HOOKS.new" >/dev/null
sudo mv "$HOOKS.new" "$HOOKS"
test "$(sudo jq '.hooks | length' "$HOOKS")" = "0" && sudo rm "$HOOKS"
sudo rm -rf "<arrakis-hook-install-dir>"Windows (PowerShell, admin):
$Path = 'C:\ProgramData\Cursor\hooks.json'
$ArrakisDir = '<arrakis-hook-install-dir>' # from Arrakis platform
$MetaField = '<arrakis-meta-field>' # from Arrakis platform
Copy-Item $Path "$Path.bak.$(Get-Date -Format yyyyMMddHHmmss)"
$cfg = Get-Content $Path -Raw | ConvertFrom-Json
foreach ($k in @($cfg.hooks.PSObject.Properties.Name)) {
$kept = @($cfg.hooks.$k | Where-Object {
($_.$MetaField.owner -ne 'arrakis') -and
($_.command -notlike "$ArrakisDir*")
})
if ($kept.Count -eq 0) { $cfg.hooks.PSObject.Properties.Remove($k) }
else { $cfg.hooks.$k = $kept }
}
$cfg | ConvertTo-Json -Depth 10 | Set-Content $Path -Encoding UTF8
Remove-Item -Recurse -Force $ArrakisDirVerification (Cursor hook)
jq '..|.command? // empty' hooks.json | grep -i arrakisreturns nothing.- The Arrakis install directory no longer exists.
ps -ef | grep -i arrakis(or Windows equivalent) shows no running hook instance.
Codex wrapper
What was installed
Per Deployment → OpenAI Codex, Arrakis ships a wrapper on PATH ahead of the real codex, plus a launchd plist (macOS) / scheduled-task (Windows) / profile.d shim (Linux) setting OTel and tenant-specific env.
Removal commands
Substitute <arrakis-codex-wrapper>, <arrakis-path-shim>, <arrakis-launchd-plist>, <arrakis-profiled-shim>, and <arrakis-codex-install-dir> with the values listed in the Arrakis platform under Settings → Integrations → Deployment → Offboarding. The full list of OTel and tenant-specific env-var names to clear is also published there.
macOS:
sudo rm -f <arrakis-codex-wrapper>
sudo rm -f <arrakis-path-shim>
sudo launchctl bootout system <arrakis-launchd-plist> 2>/dev/null
sudo rm -f <arrakis-launchd-plist>
launchctl bootout gui/$UID <user-arrakis-launchd-plist> 2>/dev/null
rm -f <user-arrakis-launchd-plist>
which -a codexWindows (PowerShell, admin):
Remove-Item -Recurse -Force '<arrakis-codex-install-dir>'
$p = [Environment]::GetEnvironmentVariable('Path','Machine') -split ';' |
Where-Object { $_ -notmatch [regex]::Escape('<arrakis-codex-install-dir>') } | ? { $_ }
[Environment]::SetEnvironmentVariable('Path', ($p -join ';'), 'Machine')
# Clear OTel + tenant-specific env vars listed in the Arrakis platform Offboarding page:
'OTEL_EXPORTER_OTLP_PROTOCOL','OTEL_EXPORTER_OTLP_ENDPOINT','OTEL_EXPORTER_OTLP_HEADERS',
'OTEL_RESOURCE_ATTRIBUTES' |
ForEach-Object {
[Environment]::SetEnvironmentVariable($_, $null, 'Machine')
[Environment]::SetEnvironmentVariable($_, $null, 'User')
}
where.exe codexLinux / WSL:
sudo rm -f <arrakis-codex-wrapper>
sudo rm -f <arrakis-profiled-shim>
sudo sed -i '/# arrakis:codex:start/,/# arrakis:codex:end/d' /etc/profile /etc/zshenv 2>/dev/null
which -a codexVerification (Codex)
which -a codex (POSIX) or where.exe codex (Windows) must not return any path under an Arrakis directory. Running codex --version from a fresh shell must not print any OTel-exporter banner.
MDM-side withdrawal nuances
When a profile is unscoped at the MDM, the OS does not always clean up the keys it set. Be explicit per vendor.
Jamf Pro
When a configuration profile is unscoped, Jamf issues a RemoveProfile MDM command. macOS removes the profile from /Library/Managed Preferences/<user>/<domain>.plist for first-party Apple-defined payloads. Custom Settings payloads (com.anthropic.claudecode) are removed at the same time, but cfprefsd’s cache may serve stale defaults reads until the next user login or a cfprefsd restart. Files dropped via Files & Processes payloads (the Cursor hook, the Codex wrapper) are NOT removed automatically — Jamf has no inverse of Files & Processes; ship a removal Policy.
(Cite: http://learn.jamf.com/r/en-US/jamf-pro-documentation-current/Configuration_Profiles )
Microsoft Intune
Settings Catalog policies that map to Policy CSPs (ADMX-backed) untattoo automatically when the assignment is removed — the registry key under HKLM\SOFTWARE\Policies\… is cleared. However, the Arrakis HKLM\SOFTWARE\Policies\ClaudeCode\ManagedSettings value is NOT a Policy CSP. It is a custom REG_SZ written by a PowerShell script payload (see Deployment → Claude Code → Microsoft Intune). Intune will not auto-remove this on policy removal. Customers must run the explicit uninstall PowerShell from the Removal commands section above. Win32-app payloads (the Codex wrapper) honor the uninstall command supplied at packaging — confirm the wrapper’s .intunewin package was built with a working uninstall verb.
Iru
Iru Custom Profile Library Items behave like Jamf Custom Settings payloads — when the Library Item is unassigned, the profile is removed and /Library/Managed Preferences/ clears. Custom Script Library Items installed with Continuously Enforced must be flipped to Run On-Demand with an uninstall payload, then triggered, then unassigned. Otherwise Iru will re-deploy the bytes at the next agent check-in.
(Cite: https://support.kandji.io/api (Iru’s support docs, still hosted on the kandji.io domain at time of writing) and the Iru Library Items documentation.)
OTel egress shutoff verification
Three checks plus belt-and-braces.
Process check
- macOS:
ps -axww -o pid,command -E | grep -E 'arrakis|<tenant-ingest-host>'andlsof -i -n | grep -E '4317|4318'. - Linux:
ps -e --format pid,cmd eandss -tnp 'dst :4317'. - Windows: Sysinternals Process Explorer → Properties → Environment, or Sysmon Event ID 1 (process create) when configured to record env.
Network check
- macOS:
sudo tcpdump -nn host <tenant-ingest-host>for 5 minutes during active use;sudo lsof -nP -iTCP -sTCP:ESTABLISHED | grep '<tenant-ingest-host>'. - Linux:
ss -tnp 'dst :4317';dig +short <tenant-ingest-host>. - Windows:
Get-NetTCPConnection -State Established; Sysmon Event ID 3 (network connect) filtered by destination.
Force-flush before final verification
Per the OpenTelemetry specification (https://opentelemetry.io/docs/specs/otel/trace/sdk/#batching-processor ), OTel batch processors flush on Shutdown or ForceFlush; defaults are scheduledDelayMillis=5000 and exportTimeoutMillis=30000. Automatic flush on SIGTERM / SIGINT is not mandated and is SDK-language-specific. Practical guidance: fully exit Claude Code (/exit), wait at least 30 seconds for the export timeout to elapse, then run the verification probes. Claude Code does not document a public force-flush command.
DNS / firewall belt-and-braces
For one week post-offboarding, add the tenant ingest hostname (listed in the Arrakis platform under Settings → Integrations → Deployment → Offboarding) to:
- The egress firewall block list, AND
- The corporate DNS resolver as
NXDOMAINor sinkhole.
This catches any lingering process, scheduled task, or stale shell session that survives the cleanup script.
Verification checklist
- MDM console: every Arrakis-owned profile / library item shows “withdrawn” or no longer exists.
- Endpoint cleanup script ran on every developer machine and exited 0.
- Per-tool verification probes (Claude Code
/doctor, Cursorjqhook check, Codexwhich -a codex) report clean. - Network check: zero outbound packets to the Arrakis ingest hostname over a 24-hour window.
- Arrakis platform credential is revoked at the customer MDM.