Agents in CI pipelines
A coding agent that runs unattended in a build (Azure DevOps, GitHub Actions, …) starts a new session on a fresh machine for every run and has no user identity that survives between runs. Give it a name through a resource attribute and all its runs roll up under one actor, tagged as an agent:
# Azure DevOps step running Claude Code headless
- script: claude -p "Review this pull request"
env:
CLAUDE_CODE_ENABLE_TELEMETRY: "1"
OTEL_LOGS_EXPORTER: otlp
OTEL_METRICS_EXPORTER: otlp
OTEL_EXPORTER_OTLP_PROTOCOL: http/json
OTEL_EXPORTER_OTLP_ENDPOINT: https://costpilot.example.com
OTEL_EXPORTER_OTLP_HEADERS: Authorization=Bearer $(COSTPILOT_INGEST_TOKEN)
OTEL_RESOURCE_ATTRIBUTES: costpilot.agent=pr-reviewer,pipeline=$(Build.DefinitionName),build_id=$(Build.BuildId)
costpilot.agentis the actor name. Use one name per job the agent does (pr-reviewer,test-fixer), not per run. It wins over any user the pipeline's account reports. Usage without it counts as human when it carries a user, and as "No actor" otherwise (e.g. product traffic).- Every other attribute (
pipeline,build_id, …) is kept as a custom dimension, e.g. to group the runs of one build, except the reserved namespaces (service.,telemetry.,os.,host.,process.,user.,session.,costpilot.and the like). Values must not contain spaces or commas unless percent-encoded. - The prompt hook does not run in a pipeline; to attribute a run to an
issue, add
issue_id=<key>to the same attribute list, andrepo=<owner/name>so the run lands in the project that lists the repository. A resource attributeissue_idalways wins over hook reports. - A pipeline run counts as one turn.
In the dashboard the Actor tile lists humans and agents side by side; the Actor type dimension separates them. An agent's drilldown shows runs and cost per run.