Coding-Agent Telemetry
AI coding agents emit OpenTelemetry (OTLP) data that AgenticAnts ingests into per-prompt traces — sessions, token counts, models, tool calls — attributed to the user and device. Claude Code, OpenAI Codex CLI, and GitHub Copilot CLI export OTLP natively; Gemini CLI and Cursor need an extra hop (below).
Content capture is off by default (except Gemini). Each tool ships usage metadata — counts, models, durations, tool names — out of the box. Prompt and response content only flows once you set the per-tool capture flag in the setup below. Leave the flag unset to keep prompts/responses out of the telemetry entirely.
How it works
Each tool exports OTLP over HTTP to one ingest endpoint, authenticated with your
project keys. AgenticAnts detects which tool sent the batch (from the OTel
service.name / instrumentation scope) and parses it into traces.
| Value | |
|---|---|
| Endpoint (base) | https://<your-agenticants-host>/api/public/otel |
| Signal paths | /v1/traces, /v1/logs (most SDKs append these automatically) |
| Protocol | http/protobuf or http/json (both accepted) |
| Auth | HTTP Basic — your project's public + secret API key |
Replace <your-agenticants-host> with your platform host (e.g.
api.agenticants.ai). Get the public (pk-...) and secret (sk-...)
keys from Settings → API Keys.
Authentication
The endpoint uses standard HTTP Basic auth. Build the header value from your two keys, joined by a colon and Base64-encoded:
Then send it via the OTLP headers variable (or the TOML headers table for
Codex):
The endpoint authenticates only via Authorization: Basic (it needs the
secret key). There are no X-Shadow-Ai-* headers — send the Base64 Basic
header shown above.
Ingest flow
User attribution
AgenticAnts attributes every trace to a user from the resource attribute
user.id (also accepted as enduser.id / user.account_id). This is the
one rule that matters across all five tools:
Set user.id, never rely on user.email. AgenticAnts attributes on
user.id only. A resource-level user.email is intentionally ignored as an
override — a stale value pushed from MDM or a shell profile must never
clobber a tool's own native login. Every OTEL_RESOURCE_ATTRIBUTES example in
this page uses user.id.
The tools differ in whether they self-identify the user. Claude Code and Codex
stamp their own login on every span/event and AgenticAnts reads it, so a manual
user.id is only an override. GitHub Copilot ships no readable user, so
user.id is mandatory for a readable attribution.
| Provider | Native user on the wire? | Auto-attributes? | How to set a readable user |
|---|---|---|---|
| Claude Code | Yes — stamps user.email from your Claude login on every span | Yes, automatic | Optional. Set user.id in OTEL_RESOURCE_ATTRIBUTES only to override (e.g. a shared/service account). |
| OpenAI Codex | Yes — stamps user.email + user.account_id from your ChatGPT login on every event | Yes, automatic | Optional. Set user.id in OTEL_RESOURCE_ATTRIBUTES to override (Codex forwards the shell var to the OTLP resource). |
| GitHub Copilot | No — only enduser.pseudo.id, a salted, non-reversible hash (e.g. f454803c…) | Only to the opaque hash | Required. Set user.id=<email> in OTEL_RESOURCE_ATTRIBUTES to get a readable user. |
| Gemini CLI | No readable user | No | Set user.id on the resource (via the collector or the launching shell). |
| Cursor | Depends on the hook | No | Set user.id in the hook's environment. |
Server-side fallback. The ingestion server has a global
OTEL_CLI_AGENT_FALLBACK_USER that attributes any otherwise-unattributed
CLI-agent trace to one user — handy for single-user / dev setups. It does not
replace per-device user.id for a real fleet.
Setup
Set the values for each coding agent you use. On macOS you can set env vars
session-wide with launchctl setenv; otherwise add them to your shell profile.
For fleet rollout see Enterprise MDM rollout.
The OTEL_SERVICE_NAME leak. The Claude Code setup exports
OTEL_SERVICE_NAME="claude-code". Any other agent launched in the same
shell inherits it and gets mis-attributed (a Copilot/Codex run shows up as
Claude Code). Run each agent in a fresh shell, or pin OTEL_SERVICE_NAME
explicitly per tool as shown below.
Claude Code reads standard OTEL_* environment variables. Add these to your
shell profile (~/.zshrc / ~/.bashrc), or on macOS set them session-wide with
launchctl setenv:
Assistant response text arrives only via OTEL_LOG_RAW_API_BODIES;
extended-thinking is always redacted. To capture only usage metadata, drop the
four content flags. Restart the terminal / Claude Code session so the variables
are picked up.
What each tool can export
The "[… not exported by …]" text you may see in a trace is by design — it marks a field the tool's telemetry genuinely does not carry, not an ingestion bug. This matrix shows what is achievable once each tool's content flag is on.
| Field | Claude Code | Copilot | Codex | Gemini |
|---|---|---|---|---|
| User prompt | ✓ OTEL_LOG_USER_PROMPTS | ✓ OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT | ✓ log_user_prompt | ✓ logPrompts (default on) |
| Assistant text | ✓ via OTEL_LOG_RAW_API_BODIES (thinking redacted) | ✓ | ✗ never emitted | ✓ |
| Tool name | ✓ | ✓ | ✓ | ✓ |
| Tool args | ✓ OTEL_LOG_TOOL_DETAILS | ✓ | ✓ (unconditional) | ✓ |
| Tool result | ✓ OTEL_LOG_TOOL_CONTENT | ✓ | ✓ (unconditional) | ◑ length only |
| Model · tokens · cost | ✓ | ✓ | ✓ | ✓ |
✓ available (flag may be required) · ◑ partial · ✗ not emitted. The only permanent placeholders are Codex assistant text and Gemini tool-result bodies.
Environment variable reference
| Tool | Key | Value |
|---|---|---|
| Claude Code | CLAUDE_CODE_ENABLE_TELEMETRY | 1 |
| Claude Code | CLAUDE_CODE_ENHANCED_TELEMETRY_BETA | 1 (enables tracing) |
| Claude Code | OTEL_TRACES_EXPORTER / OTEL_LOGS_EXPORTER / OTEL_METRICS_EXPORTER | otlp |
| Claude Code | OTEL_LOG_USER_PROMPTS / OTEL_LOG_TOOL_DETAILS / OTEL_LOG_TOOL_CONTENT | 1 (content; unset to redact) |
| Claude Code | OTEL_LOG_RAW_API_BODIES | 1 (assistant response text) |
| Copilot | COPILOT_OTEL_ENABLED | true |
| Copilot | COPILOT_OTEL_ENDPOINT | https://<host>/api/public/otel |
| Copilot | OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT | true (prompt/response/tool content) |
| Codex | (see ~/.codex/config.toml [otel] block; log_user_prompt = true, trace_exporter = "none") | — |
| All (native) | OTEL_EXPORTER_OTLP_ENDPOINT | https://<host>/api/public/otel |
| All (native) | OTEL_EXPORTER_OTLP_HEADERS | Authorization=Basic <base64(pk:sk)> |
| All | OTEL_SERVICE_NAME | claude-code · github-copilot · (Codex: from resource) · cursor |
| All | OTEL_RESOURCE_ATTRIBUTES | deployment.environment=production,user.id=<email> |
Use user.id to attribute a trace to a user on the Users page — that is the
key AgenticAnts reads (user.email is ignored as an override). Claude Code and
Codex auto-detect the user from their own login, so the attribute is optional
for them; GitHub Copilot has no readable user, so user.id is required for
it. See User attribution for the full per-provider
breakdown. When the agent sets host.id=<device-uuid>, the trace also ties to a
specific device.
Verify it's working
Confirm the variables are set
Generate activity
Run a prompt in the tool (ask Claude Code a question, or run a Codex / Copilot command that calls a tool).
Check AgenticAnts
Open AgenticAnts — each prompt appears as its own trace within a minute or two, grouped into a session on the Sessions tab and attributed on Users.
Troubleshooting
Nothing shows up
- Endpoint reachable? An unauthenticated POST should return
401(auth required), not404:Abashcurl -s -o /dev/null -w "%{http_code}\n" -X POST \ -H "Content-Type: application/json" --data '{}' \ https://<your-agenticants-host>/api/public/otel/v1/logs404means the host/path is wrong — the base is/api/public/otel. 401errors? Check the Base64pk:skvalue and that the keys belong to the right project.- Wrong tool / everything shows as Claude Code? A stale
OTEL_SERVICE_NAME=claude-codein the shell. Pin it per tool or use a fresh shell.
Codex floods the trace list
Set trace_exporter = "none" — the trace exporter is internal-span noise. Keep
only the logs exporter with the /v1/logs path.