Skip to Content
DeploymentOffboarding

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

  1. Arrakis-initiated profile withdrawal. Arrakis publishes an empty / withdrawn version of every Arrakis-owned configuration profile (Claude Code managed-settings.d fragments, Cursor enterprise hooks.json entries, 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.
  2. 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).
  3. 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.
  4. 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)

OSArrakis-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
WindowsC:\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 claudecode

Verification 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 SilentlyContinue

Verification 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-only

Verification 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 --debug then 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) or Get-ChildItem Env: | Where-Object Name -match '^(CLAUDE_CODE|OTEL_)' (PowerShell) — empty.
  • macOS additional: defaults read /Library/Managed\ Preferences/com.anthropic.claudecode 2>/dev/null should 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

ScopePath
Enterprise / system-widemacOS: /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):

  1. The command path is rooted at an Arrakis-owned absolute prefix.
  2. 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 $ArrakisDir

Verification (Cursor hook)

  • jq '..|.command? // empty' hooks.json | grep -i arrakis returns 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 codex

Windows (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 codex

Linux / 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 codex

Verification (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.

(Cite: https://learn.microsoft.com/en-us/intune/intune-service/configuration/administrative-templates-windows )

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>' and lsof -i -n | grep -E '4317|4318'.
  • Linux: ps -e --format pid,cmd e and ss -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 NXDOMAIN or sinkhole.

This catches any lingering process, scheduled task, or stale shell session that survives the cleanup script.

Verification checklist

  1. MDM console: every Arrakis-owned profile / library item shows “withdrawn” or no longer exists.
  2. Endpoint cleanup script ran on every developer machine and exited 0.
  3. Per-tool verification probes (Claude Code /doctor, Cursor jq hook check, Codex which -a codex) report clean.
  4. Network check: zero outbound packets to the Arrakis ingest hostname over a 24-hour window.
  5. Arrakis platform credential is revoked at the customer MDM.

Cross-references

Last updated on