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
| Tool | Surface | Fleet mechanism |
|---|---|---|
| Claude Code | env vars | managed /etc/zshenv or launchctl setenv (macOS); machine/user env vars (Windows) |
| GitHub Copilot CLI | env vars | same as Claude Code |
| OpenAI Codex CLI | ~/.codex/config.toml | config-profile / script payload (macOS); Intune script (Windows) |
| Cursor | env vars + ~/.cursor/hooks.json | env-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.id — not 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:
| Tool | Auto-attributes? | user.id in MDM payload? |
|---|---|---|
| Claude Code | Yes — from each user's Claude login | Optional (only to force a shared/service account) |
| OpenAI Codex | Yes — from each user's ChatGPT login | Optional (only to override) |
| GitHub Copilot | No — emits only a non-reversible enduser.pseudo.id hash | Required for a readable user |
| Cursor / Gemini | No | Set 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):
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:
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:
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/*:
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.