OpenTelemetry monitoring
The Copilot runtime can export traces and metrics via OpenTelemetry (OTel), giving you visibility into agent interactions, LLM calls, tool executions, and token usage. GenAI signal names and attributes follow the OTel GenAI Semantic Conventions. The runtime also emits Copilot-specific signals under the github.copilot.* namespace.
The configuration and telemetry tables in this article describe the shared Copilot runtime used by these clients and harnesses:
- Copilot CLI
- The GitHub Copilot app
- VS Code Copilot sessions using Agent Host
VS Code's Local agent harness has different environment variables, defaults, and telemetry signals. For example, it uses COPILOT_OTEL_CAPTURE_CONTENT for content capture, defaults service.name to copilot-chat, and emits events such as copilot_chat.session.start. The tables below do not describe that harness or extension-emitted telemetry. See Monitor agent usage with OpenTelemetry in the VS Code documentation.
OTel configuration can also be set in VS Code, or in an enterprise-wide managed-settings.json file. See Enable OTel monitoring in the VS Code documentation and Enterprise managed settings.
OTel environment variables
| Variable | Default | Description |
|---|---|---|
COPILOT_OTEL_ | false | Explicitly enable OTel. Not required if OTEL_EXPORTER_ is set. |
OTEL_EXPORTER_ | — | OTLP endpoint URL. Setting this automatically enables OTel. |
COPILOT_OTEL_ | otlp-http | Exporter type: otlp-http or file. Auto-selects file when COPILOT_OTEL_ is set. |
OTEL_EXPORTER_ | http/json | OTLP HTTP wire protocol: http/json or http/protobuf. Only applies to the otlp-http exporter. |
OTEL_EXPORTER_ | — | Override OTEL_EXPORTER_ for traces only. |
OTEL_EXPORTER_ | — | Override OTEL_EXPORTER_ for metrics only. |
OTEL_SERVICE_NAME | github-copilot | Service name in resource attributes. |
OTEL_RESOURCE_ | — | Extra resource attributes as comma-separated key=value pairs. Use percent-encoding for special characters. |
OTEL_INSTRUMENTATION_ | NO_CONTENT | Capture full prompt and response content. Accepts NO_CONTENT, SPAN_ONLY, SPAN_AND_EVENT, or EVENT_ONLY. See Content capture. |
OTEL_INSTRUMENTATION_ | false | Legacy boolean fallback, used only when the standard content variable is empty or unset. See Content capture. |
OTEL_LOG_LEVEL | — | OTel diagnostic log level: NONE, ERROR, WARN, INFO, DEBUG, VERBOSE, ALL. |
COPILOT_OTEL_ | — | Write all signals to this file as JSON-lines. Setting this automatically enables OTel. |
COPILOT_OTEL_ | github.copilot | Instrumentation scope name for tracer and meter. |
OTEL_EXPORTER_ | — | Authentication headers for the OTLP exporter, as comma-separated key=value pairs. |
Transport security
These variables apply only to the otlp-http exporter with an https:// endpoint, and only once OTel export is enabled. They do not enable OTel on their own, and they have no effect on the file exporter.
| Variable | Description |
|---|---|
OTEL_EXPORTER_ | Path to a PEM file with extra CA certificates to trust for the OTLP endpoint, such as a private or corporate CA. Merged with the operating system trust store, so public-CA endpoints still validate. |
OTEL_EXPORTER_ | Path to the client certificate PEM for mutual TLS (mTLS). |
OTEL_EXPORTER_ | Path to the matching unencrypted private key PEM. Both certificate and key must be set together, and passphrase-protected keys are not supported. |
Per-signal variants override the generic variables for a single signal: OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE, and the matching _CLIENT_CERTIFICATE and _CLIENT_KEY names.
Warning
If the otlp-http exporter resolves to an http:// endpoint, including the default http://localhost:4318, export is disabled rather than sent in cleartext. Startup is not aborted and nothing is reported on the console—the only signal is a warning in the process log. NODE_EXTRA_CA_CERTS does not affect the OTLP exporter, which uses a native HTTP transport, so use the variables above instead.
Dynamic authentication headers
In Copilot CLI, set telemetry.headersProvider in your user settings or in an enterprise-managed telemetry policy to run a command that supplies fresh OTLP headers. Repository settings never apply this setting.
{
"telemetry": {
"headersProvider": {
"path": "node",
"args": ["bin/otel-headers.js"],
"refreshIntervalMs": 1740000
}
}
}
| Field | Description |
|---|---|
path | Executable name, absolute path, or home-relative path. This is not a shell command, and workspace-relative paths are rejected. |
args | Optional arguments, passed literally. Select an interpreter explicitly for scripts (for example, node). |
refreshIntervalMs | Optional refresh interval. Default: 29 minutes. |
Set headersProvider to false to disable dynamic headers.
- The command runs from your home directory with a 30-second deadline. Its stdout must be a JSON object of string header values. An empty object clears dynamic headers.
- The provider runs only for active HTTP OTLP exporters. Use HTTPS, because an HTTP endpoint produces a warning and OTLP redirects are rejected.
- Header precedence, from highest to lowest: managed helper output, managed static headers, user helper output, then
OTEL_EXPORTER_OTLP_HEADERS. A managed helper overrides a user helper, and a managedfalsedisables helpers. - If a refresh fails, exports that need dynamic headers are blocked until the next refresh.
Traces
The runtime emits a hierarchical span tree for each agent interaction. Each tree contains an invoke_agent root span, with chat and execute_tool child spans. In plan mode, an intermediate plan span wraps the task-decomposition work and parents its own chat and execute_tool spans. chat spans are named chat {model}, falling back to chat when the model is unavailable. Subagent invocations are linked into the same trace by context propagation.
invoke_agent span attributes
Wraps the entire agent invocation: all LLM calls and tool executions for one user message.
- Both top-level sessions and subagent invocations (for example, explore, task) use span kind
INTERNAL(in-process); provider-facing inference is represented by childCLIENTchatspans. - Top-level sessions additionally carry
server.addressandserver.port; subagent invocations do not.
| Attribute | Description | Scope |
|---|---|---|
gen_ai.operation.name | invoke_agent | Both |
gen_ai.provider.name | Provider (for example, github, anthropic) | Both |
gen_ai.agent.id | Stable agent-definition identifier when known; top-level default uses github.copilot.default | Both |
gen_ai.agent.name | Agent name (when available) | Both |
gen_ai.agent.description | Agent description (when available) | Both |
gen_ai.agent.version | Agent definition version when known; otherwise runtime version | Both |
gen_ai.conversation.id | Session identifier | Both |
enduser.pseudo.id | Pseudonymous Copilot user identifier from analytics_tracking_ when available | Both |
gen_ai.request.model | Requested model | Both |
gen_ai.response.finish_reasons | ["stop"] or ["error"] | Both |
gen_ai.usage.input_tokens | Total input tokens (all turns) | Both |
gen_ai.usage.output_tokens | Total output tokens (all turns) | Both |
gen_ai.usage.cache_read.input_tokens | Cached input tokens read | Both |
gen_ai.usage.cache_write.input_tokens | Cached input tokens written | Both |
gen_ai.usage.reasoning.output_tokens | Reasoning output tokens | Both |
github.copilot.turn_count | Number of LLM round-trips | Both |
github.copilot.cost | Per-request model multiplier used for billing. Not a monetary value. | Both |
github.copilot.nano_aiu | AI units consumed, in nano AI units (1 AIU = 1,000,000,000 nano AIU) | Both |
server.address | Server hostname | Top-level only |
server.port | Server port | Top-level only |
error.type | Error class name (on error) | Both |
gen_ai.input.messages | Full input messages as JSON (content capture only) | Both |
gen_ai.output.messages | Full output messages as JSON (content capture only) | Both |
gen_ai.system_instructions | System prompt content as JSON (content capture only) | Both |
gen_ai.tool.definitions | Tool schemas as JSON (content capture only) | Both |
chat span attributes
One span per LLM request. Span kind: CLIENT.
| Attribute | Description |
|---|---|
gen_ai.operation.name | chat |
gen_ai.provider.name | Provider name |
gen_ai.request.model | Requested model |
gen_ai.request.stream | Whether streaming mode was used (streaming only) |
gen_ai.conversation.id | Session identifier |
gen_ai.response.finish_reasons | Stop reasons |
gen_ai.response.id | Response ID |
gen_ai.response.model | Resolved model |
gen_ai.response.time_to_first_ | Time to first streaming chunk, in seconds (streaming only) |
gen_ai.usage.cache_write.input_tokens | Cached tokens written |
gen_ai.usage.cache_read.input_tokens | Cached tokens read |
gen_ai.usage.input_tokens | Input tokens this turn |
gen_ai.usage.output_tokens | Output tokens this turn |
gen_ai.usage.reasoning.output_tokens | Reasoning output tokens this turn |
github.copilot.request.type | Set to compaction on spans emitted for conversation compaction |
github.copilot.cost | Per-request model multiplier used for billing. Not a monetary value. |
github.copilot.nano_aiu | AI units consumed this turn, in nano AI units (1 AIU = 1,000,000,000 nano AIU) |
github.copilot.server_duration | Server-side duration |
github.copilot.initiator | Request initiator |
github.copilot.turn_id | Turn identifier |
github.copilot.interaction_id | Interaction identifier |
server.address | Server hostname |
server.port | Server port |
error.type | Error class name (on error) |
gen_ai.input.messages | Full prompt messages as JSON (content capture only) |
gen_ai.output.messages | Full response messages as JSON (content capture only) |
gen_ai.system_instructions | System prompt content as JSON (content capture only) |
execute_tool span attributes
One span per tool call. Span kind: INTERNAL.
| Attribute | Description |
|---|---|
gen_ai.operation.name | execute_tool |
gen_ai.provider.name | Provider name (when available) |
gen_ai.tool.name | Tool name (for example, readFile) |
gen_ai.tool.type | function |
gen_ai.tool.call.id | Tool call identifier |
gen_ai.tool.description | Tool description |
error.type | Error class name (on error) |
gen_ai.tool.call.arguments | Tool input arguments as JSON (content capture only) |
gen_ai.tool.call.result | Tool output as JSON (content capture only) |
Note
To measure AI unit consumption, read github.copilot.nano_aiu from the root invoke_agent span only. The attribute is also stamped on the child chat spans, so summing it across every span double-counts. github.copilot.cost is a per-request model multiplier used for billing calculations—it is not a currency value and must not be interpreted as one.
Metrics
The following metrics are emitted.
GenAI convention metrics
| Metric | Type | Unit | Description |
|---|---|---|---|
gen_ai.client.operation.duration | Histogram | s | Duration of a GenAI operation other than inference |
gen_ai.client.inference.duration | Histogram | s | Duration of a single LLM API call |
gen_ai.client.inference.operation.input_tokens | Histogram | {token} | Input tokens per model call |
gen_ai.client.inference.operation.output_tokens | Histogram | {token} | Output tokens per model call |
gen_ai.client.inference.usage.input_tokens | Counter | {token} | Input tokens used, including cached tokens |
gen_ai.client.inference.usage.output_tokens | Counter | {token} | Output tokens used, including reasoning tokens |
gen_ai.client.inference.usage.cache_read.input_tokens | Counter | {token} | Input tokens served from a provider-managed cache |
gen_ai.client.inference.usage.cache_write.input_tokens | Counter | {token} | Input tokens written to a provider-managed cache |
gen_ai.client.inference.usage.reasoning.output_tokens | Counter | {token} | Output tokens used for reasoning |
gen_ai.client.inference.time_to_first_ | Histogram | s | Time to receive the first chunk of a streaming response |
gen_ai.client.inference.time_per_output_ | Histogram | s | Inter-chunk latency after the first chunk |
gen_ai.invoke_agent.duration | Histogram | s | End-to-end duration of one agent invocation |
gen_ai.invoke_agent.inference_ | Histogram | {inference_call} | Number of model calls made during one agent invocation, counted at provider dispatch (failed and partial calls included; requests blocked before dispatch excluded). Dimension: gen_ai.agent.name. |
gen_ai.invoke_agent.tool_ | Histogram | {tool_call} | Number of client-side tool calls made during one agent invocation (failed and partial calls included; synthetic tool lifecycles and provider-executed server-side tools excluded). Dimension: gen_ai.agent.name. |
gen_ai.execute_tool.duration | Histogram | s | Duration of a single tool execution |
gen_ai.invoke_workflow.duration | Histogram | s | End-to-end duration of a GenAI workflow (for example, a fleet mode run orchestrating parallel subagents). |
Token metrics aggregate foreground and compaction work. github.copilot.request.type is a span attribute only, so it is not available as a metric dimension for separating them.
Vendor-specific metrics
| Metric | Type | Unit | Description |
|---|---|---|---|
github.copilot.tool.call.count | Counter | {call} | Tool invocations by gen_ai.tool.name and success |
github.copilot.tool.call.duration | Histogram | s | Tool execution latency by gen_ai.tool.name |
github.copilot.agent.turn.count | Histogram | {turn} | LLM round-trips per agent invocation |
github.copilot.mcp.server.connection.count | Counter | {attempt} | Completed MCP server connection attempts by transport and outcome |
github.copilot.code.lines_added | Counter | {line} | Lines added by file-editing tools, recorded in real time |
github.copilot.code.lines_removed | Counter | {line} | Lines removed by file-editing tools, recorded in real time |
github.copilot.sandbox.operation.count | Counter | {decision} | Bounded sandbox governance decisions, deduplicated |
github.copilot.sandbox.spawn.duration | Histogram | s | Sandboxed process spawn latency |
github.copilot.sandbox.policy.path.count | Histogram | {path} | Effective filesystem policy paths by access class |
Note
Sandbox metrics are bounded operational signals, not a complete audit log. An unchanged policy resolution is deduplicated rather than counted again, and these metrics never carry file paths, commands, or proxy URLs as dimensions.
Span events
Key lifecycle events recorded on the active chat or invoke_agent span. This is not an exhaustive list; the runtime also emits sandbox governance events and additional session events.
| Event | Description | Key attributes |
|---|---|---|
github.copilot.hook.start | A hook began executing | github.copilot.hook.type, github.copilot.hook.invocation_id |
github.copilot.hook.end | A hook completed successfully | github.copilot.hook.type, github.copilot.hook.invocation_id |
github.copilot.hook.error | A hook failed | github.copilot.hook.type, github.copilot.hook.invocation_id, github.copilot.hook.error_message |
github.copilot.session.truncation | Conversation history was truncated | github.copilot.token_limit, github.copilot.pre_tokens, github.copilot.post_tokens, github.copilot.pre_messages, github.copilot.post_messages, github.copilot.tokens_removed, github.copilot.messages_removed, github.copilot.performed_by |
github.copilot.session.compaction_start | History compaction began | None |
github.copilot.session.compaction_complete | History compaction completed | github.copilot.success, github.copilot.pre_tokens, github.copilot.post_tokens, github.copilot.tokens_removed, github.copilot.messages_removed, github.copilot.message (content capture only) |
github.copilot.skill.invoked | A skill was invoked | github.copilot.skill.name, github.copilot.skill.path, github.copilot.skill.plugin_name, github.copilot.skill.plugin_version |
github.copilot.session.shutdown | Session is shutting down | github.copilot.shutdown_type, github.copilot.total_premium_, github.copilot.lines_added, github.copilot.lines_removed, github.copilot.files_modified_ |
github.copilot.session.abort | User cancelled the current operation | github.copilot.abort_reason |
gen_ai.client.operation.exception | Session error | github.copilot.error_type, github.copilot.error_status_, github.copilot.error_provider_ |
Resource attributes
All signals carry these resource attributes.
| Attribute | Value |
|---|---|
service.name | github-copilot (configurable via OTEL_SERVICE_NAME) |
service.version | Runtime version |
Content capture
By default, no prompt content, responses, or tool arguments are captured—only metadata like model names, token counts, and durations. To capture full content, set OTEL_INSTRUMENTATION_GENAI_CAPTURE_CONTENT to SPAN_ONLY or SPAN_AND_EVENT. The values NO_CONTENT (the default) and EVENT_ONLY leave span content capture off.
If OTEL_INSTRUMENTATION_GENAI_CAPTURE_CONTENT is empty or unset, the runtime falls back to the legacy boolean variable OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT. An unrecognized value for the standard variable disables span content capture and logs a warning.
Warning
Content capture may include sensitive information such as code, file contents, and user prompts. Only enable this in trusted environments.
When content capture is enabled, the following attributes are populated.
| Attribute | Content |
|---|---|
gen_ai.input.messages | Full prompt messages (JSON) |
gen_ai.output.messages | Full response messages (JSON) |
gen_ai.system_instructions | System prompt content (JSON) |
gen_ai.tool.definitions | Tool schemas (JSON). Schema metadata keeps an independent opt-in: this attribute is populated only by the legacy OTEL_INSTRUMENTATION_ variable, not by OTEL_INSTRUMENTATION_. |
gen_ai.tool.call.arguments | Tool input arguments |
gen_ai.tool.call.result | Tool output |