Docs/Integrations/Coding Agent Telemetry Mdm

Enterprise MDM Rollout

Coding-Agent Telemetry is configured through environment variables (Claude Code, Copilot, Cursor) or a config file (Codex ~/.codex/config.toml). This page covers pushing that config fleet-wide with your MDM.

Prerequisites. Admin access to your MDM (Jamf, Kandji, Intune) or Group Policy, and a project public + secret key from Settings → API Keys. Pre-compute the Basic auth value once: printf 'pk-...:sk-...' | base64.

What can be managed how

ToolSurfaceFleet mechanism
Claude Codeenv varsmanaged /etc/zshenv or launchctl setenv (macOS); machine/user env vars (Windows)
GitHub Copilot CLIenv varssame as Claude Code
OpenAI Codex CLI~/.codex/config.tomlconfig-profile / script payload (macOS); Intune script (Windows)
Cursorenv vars + ~/.cursor/hooks.jsonenv-var payload + a managed hooks file

Pin OTEL_SERVICE_NAME per tool. A single shared OTEL_SERVICE_NAME pushed fleet-wide makes every agent mis-attribute (all traffic shows up as one tool). Scope it: claude-code for Claude Code, github-copilot for Copilot, cursor for Cursor. Codex takes its service name from its own resource, so don't set OTEL_SERVICE_NAME for Codex hosts.

User attribution across the fleet

AgenticAnts attributes a trace to a user from the resource attribute user.idnot user.email, which is intentionally ignored as an override so a stale templated value can't clobber a tool's native login. How each tool needs to be provisioned:

ToolAuto-attributes?user.id in MDM payload?
Claude CodeYes — from each user's Claude loginOptional (only to force a shared/service account)
OpenAI CodexYes — from each user's ChatGPT loginOptional (only to override)
GitHub CopilotNo — emits only a non-reversible enduser.pseudo.id hashRequired for a readable user
Cursor / GeminiNoSet per device if you want a readable user

Per-user user.id must be templated per device — it can't be one shared value. Claude Code and Codex auto-derive the user, so leave user.id out of their payload (a shared value would mis-attribute the whole fleet to one person). For GitHub Copilot, template user.id=<email> per device from your MDM's primary-user identity. There is no universal variable name — populate it from a Jamf script parameter (e.g. $4, sourced from an LDAP/extension-attribute lookup), a Kandji assignment/Blueprint variable, or whoami /upn on an AzureAD-joined Windows host (Intune). The examples below show the mechanism for each.

Env-var tools (Claude Code, Copilot, Cursor)

Option A — managed /etc/zshenv (recommended). Distribute a script payload that writes a shell-wide env file. zshenv is sourced by every zsh invocation (login, non-login, scripts), so the variables reach the agent however it is launched.

Claude Code (auto-attributes — no user.id needed):

bash
# Deployed by Jamf policy / Kandji custom script / Intune shell script, run as root cat > /etc/zshenv <<'EOF' export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.agenticants.ai/api/public/otel" export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <base64(pk:sk)>" # Claude Code fleet: export CLAUDE_CODE_ENABLE_TELEMETRY=1 export CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 export OTEL_TRACES_EXPORTER=otlp export OTEL_LOGS_EXPORTER=otlp export OTEL_SERVICE_NAME="claude-code" export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production" export OTEL_LOG_USER_PROMPTS=1 EOF chmod 644 /etc/zshenv

GitHub Copilot (must template a per-device user.id). The Jamf/Kandji script runs as root, so the device's assigned email has to come from the MDM — there's no built-in email variable. In Jamf, pass it as a script parameter ($4, populated from an LDAP / extension-attribute lookup); Kandji exposes it via a Blueprint/assignment variable:

bash
# Jamf passes the assigned-user email as a script parameter ($4), sourced from # your LDAP/extension-attribute mapping. (There is no built-in $EMAIL variable.) CONSOLE_EMAIL="$4" # e.g. jane.doe@acme.com — templated by the MDM per device cat > /etc/zshenv <<EOF export OTEL_EXPORTER_OTLP_ENDPOINT="https://api.agenticants.ai/api/public/otel" export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic <base64(pk:sk)>" # GitHub Copilot fleet: export COPILOT_OTEL_ENABLED=true export COPILOT_OTEL_ENDPOINT="https://api.agenticants.ai/api/public/otel" export OTEL_SERVICE_NAME="github-copilot" export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true # REQUIRED for a readable user — Copilot emits only a non-reversible hash otherwise: export OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production,user.id=${CONSOLE_EMAIL}" EOF chmod 644 /etc/zshenv

Note the unquoted heredoc (<<EOF, not <<'EOF') in the Copilot example so ${CONSOLE_EMAIL} expands. /etc/zshenv is machine-wide, so this assumes one assigned user per device (the norm for corporate laptops) — on a shared/multi-user Mac, use the LaunchAgent (Option B), which resolves the user at login. If you run a single shared /etc/zshenv for both tools, keep OTEL_SERVICE_NAME and user.id scoped per tool — a single shared OTEL_SERVICE_NAME mis-attributes the whole fleet, and a single shared user.id attributes everyone to one person.

Option B — launchctl setenv via a LaunchAgent. Push a LaunchAgent plist (e.g. /Library/LaunchAgents/ai.agenticants.otel.plist) that runs launchctl setenv OTEL_EXPORTER_OTLP_ENDPOINT … at login. This exposes the variables to GUI-launched apps (Cursor, VS Code Copilot) that don't read zshenv. Because a LaunchAgent runs in the logged-in user's context, you can derive the email locally for the per-user Copilot user.id:

xml
<!-- /Library/LaunchAgents/ai.agenticants.otel.plist (excerpt) --> <key>ProgramArguments</key> <array> <string>/bin/sh</string> <string>-c</string> <string>launchctl setenv OTEL_EXPORTER_OTLP_ENDPOINT "https://api.agenticants.ai/api/public/otel"; launchctl setenv OTEL_EXPORTER_OTLP_HEADERS "Authorization=Basic <base64(pk:sk)>"; launchctl setenv OTEL_SERVICE_NAME "github-copilot"; launchctl setenv OTEL_RESOURCE_ATTRIBUTES "deployment.environment=production,user.id=$(id -un)@acme.com"</string> </array> <key>RunAtLoad</key><true/>

user.id recap. Claude Code and Codex auto-derive the user from each user's login, so omit user.id for them. For GitHub Copilot it is required — template user.id=<email> per device from your MDM's user inventory. AgenticAnts attributes on user.id, never on user.email.

Config-file tool (Codex)

Codex reads ~/.codex/config.toml per user — push the file to each user's home.

Distribute a script payload (Jamf policy, Kandji script, or Intune shell script) that writes the file into each user's home. Run it per-user, or iterate /Users/*:

bash
write_codex_config() { local home="$1" mkdir -p "$home/.codex" cat > "$home/.codex/config.toml" <<'EOF' [otel] environment = "production" log_user_prompt = true metrics_exporter = "none" trace_exporter = "none" exporter = { otlp-http = { endpoint = "https://api.agenticants.ai/api/public/otel/v1/logs", protocol = "json", headers = { "Authorization" = "Basic <base64(pk:sk)>" } } } EOF } for u in /Users/*; do [ -d "$u" ] && write_codex_config "$u"; done

Keep trace_exporter = "none" in every pushed Codex config — the trace exporter is internal-span noise. Codex captures tool arguments + output unconditionally, so shell commands and file contents enter telemetry even with log_user_prompt = false; account for this in your data-handling review.

Secret handling

Distribute the key through MDM-managed config, not source control

The Basic auth value embeds your secret key. Push it via the MDM payloads above (managed env file, registry, config profile) — never commit pk:sk to a repo or a checked-in dotfile.

Use a least-privilege, per-purpose key

Provision a dedicated project key for fleet telemetry so you can scope and revoke it independently of other integrations.

Rotate by re-pushing

To rotate: generate a new key in Settings → API Keys, re-compute base64(pk:sk), update the MDM payload, and re-deploy. Revoke the old key once all devices have checked in.

© 2026 ANTS Platform, Inc.Docs v1.0 · Last updated June 2026