# How to Set Up OpenTelemetry Observability and Tracing for Copilot SDK: A Complete Guide

> Learn how to set up OpenTelemetry observability and tracing for Copilot SDK. This guide explains configuring telemetry and distributed tracing for your Copilot runtime.

- Repository: [GitHub/copilot-sdk](https://github.com/github/copilot-sdk)
- Tags: how-to-guide
- Published: 2026-08-02

---

**The Copilot SDK enables OpenTelemetry observability by accepting a `TelemetryConfig` object that it translates into environment variables for the spawned Copilot runtime, with optional `TraceContextProvider` support for distributed tracing.**

The **Copilot SDK** provides a flexible, environment-variable-based approach to OpenTelemetry (OTEL) observability. Rather than bundling OpenTelemetry libraries directly, the SDK externalizes telemetry configuration—allowing you to inject OTLP endpoints, exporters, and trace context through the `telemetry` option in `CopilotClientOptions`.

## Core Architecture: How Telemetry Flows Through the SDK

The SDK's telemetry system rests on three foundational types defined in [`nodejs/src/types.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts):

- **`TelemetryConfig`** — configuration object describing exporters, endpoints, and capture settings ([source](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts#L84-L101))
- **`CopilotClientOptions.telemetry`** — optional field passed to the `CopilotClient` constructor ([source](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts#L40-L45))
- **`TraceContextProvider`** — callback interface for propagating external W3C trace context ([source](https://github.com/github/copilot-sdk/blob/main/nodejs/src/types.ts#L67-L78))

When you call `client.start()`, the SDK's `buildRuntimeEnv()` method in [`nodejs/src/client.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/client.ts) ([source](https://github.com/github/copilot-sdk/blob/main/nodejs/src/client.ts#L2384-L2394)) transforms your `TelemetryConfig` into the following environment variables:

| Environment Variable | Source in `TelemetryConfig` | Purpose |
|---|---|---|
| `COPILOT_OTEL_ENABLED` | always `"true"` | Activates the runtime's OTEL exporter |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `otlpEndpoint` | OTLP collector URL |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | `otlpProtocol` | `"http/json"` or `"http/protobuf"` |
| `COPILOT_OTEL_FILE_EXPORTER_PATH` | `filePath` | JSON-lines output for local debugging |
| `COPILOT_OTEL_EXPORTER_TYPE` | `exporterType` | `"otlp-http"` or `"file"` |
| `COPILOT_OTEL_SOURCE_NAME` | `sourceName` | Instrumentation scope name |
| `OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` | `captureContent` | Include prompt/response payloads |

## Setting Up OTLP HTTP Exporter

The most common production configuration streams traces to an OTLP collector. Pass a `TelemetryConfig` with `exporterType: "otlp-http"` when constructing your client:

```typescript
import { CopilotClient, RuntimeConnection } from "copilot-sdk";

const client = new CopilotClient({
  telemetry: {
    otlpEndpoint: "http://localhost:4318",
    otlpProtocol: "http/json",
    exporterType: "otlp-http",
    sourceName: "my-backend-service",
    captureContent: true,
  },
  connection: RuntimeConnection.forTcp({ port: 0 })
});

await client.start();

```

The `buildRuntimeEnv()` method populates `OTEL_EXPORTER_OTLP_ENDPOINT` and related variables before spawning the Copilot CLI process. The runtime reads these on startup and configures its internal exporter accordingly.

## Using File-Based Exporter for Local Development

For debugging without a collector, write traces to a JSON-lines file:

```typescript
import { CopilotClient } from "copilot-sdk";

const client = new CopilotClient({
  telemetry: {
    filePath: "./copilot-traces.jsonl",
    exporterType: "file",
    captureContent: false,
  },
});

await client.start();

```

This sets `COPILOT_OTEL_FILE_EXPORTER_PATH` ([source](https://github.com/github/copilot-sdk/blob/main/nodejs/src/client.ts#L2389-L2391)), causing the runtime to append traces as newline-delimited JSON objects.

## Implementing Distributed Tracing with TraceContextProvider

To make Copilot runtime spans children of your existing traces, provide a `TraceContextProvider` via `onGetTraceContext`:

```typescript
import { propagation, context } from "@opentelemetry/api";
import { CopilotClient } from "copilot-sdk";

const client = new CopilotClient({
  telemetry: {
    otlpEndpoint: "http://localhost:4318",
    otlpProtocol: "http/json",
    exporterType: "otlp-http",
  },
  onGetTraceContext: () => {
    const carrier: Record<string, string> = {};
    propagation.inject(context.active(), carrier);
    return carrier; // { traceparent, tracestate }
  },
});

await client.start();

```

The SDK calls this provider before each RPC (`session.create`, `session.send`, etc.) via the `getTraceContext` helper in [`nodejs/src/telemetry.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/telemetry.ts) ([source](https://github.com/github/copilot-sdk/blob/main/nodejs/src/telemetry.ts#L20-L27)), injecting the returned headers into the request.

## Complete End-to-End Example

Combine OTLP export with custom span propagation:

```typescript
import { trace, context, propagation } from "@opentelemetry/api";
import { CopilotClient, RuntimeConnection } from "copilot-sdk";

const tracer = trace.getTracer("my-app");

const client = new CopilotClient({
  telemetry: {
    otlpEndpoint: "http://localhost:4318",
    otlpProtocol: "http/json",
    exporterType: "otlp-http",
    sourceName: "my-app",
    captureContent: true,
  },
  onGetTraceContext: () => {
    const carrier: Record<string, string> = {};
    propagation.inject(context.active(), carrier);
    return carrier;
  },
  connection: RuntimeConnection.forTcp({ port: 0 }),
});

await client.start();

await tracer.startActiveSpan("copilot-session", async (span) => {
  const session = await client.session.create();
  const response = await session.send("Refactor this function to use async/await");
});

```

All Copilot CLI spans become descendants of your `"copilot-session"` span, with full trace continuity to your OTLP collector.

## Important Constraints and Limitations

**In-process connections lack per-client telemetry.** The `RuntimeConnection.forInProcess()` transport inherits the host process's environment unconditionally. If you use this connection type, set OTEL environment variables directly before constructing the client—the `telemetry` option has no effect.

**Configuration is immutable after construction.** Changes to `TelemetryConfig` after calling `client.start()` do not propagate; the SDK reads these values once during `buildRuntimeEnv()`.

**Zero OpenTelemetry dependencies.** The SDK does not import `@opentelemetry/*` packages, leaving you free to use any API version in your application code.

## Summary

- The **Copilot SDK** implements OpenTelemetry observability through environment variable injection, not bundled libraries
- Configure via **`TelemetryConfig`** in `CopilotClientOptions.telemetry` to set OTLP endpoints, protocols, and exporters
- The **`buildRuntimeEnv()`** method in [`nodejs/src/client.ts`](https://github.com/github/copilot-sdk/blob/main/nodejs/src/client.ts) translates configuration into `COPILOT_OTEL_*` and `OTEL_EXPORTER_*` variables
- Use **`TraceContextProvider`** (`onGetTraceContext`) to propagate external trace context for distributed tracing
- Choose **OTLP HTTP** for production collectors or **file export** for local debugging
- Avoid **`forInProcess`** connections when telemetry isolation is required

## Frequently Asked Questions

### Does the Copilot SDK include OpenTelemetry libraries?

No. The SDK deliberately excludes `@opentelemetry` dependencies. It writes configuration to environment variables that the spawned Copilot runtime reads, keeping your dependency tree clean and version-flexible.

### What happens if I don't provide a `telemetry` option?

The Copilot runtime runs without OpenTelemetry export. No traces are collected, and no performance overhead from telemetry processing occurs.

### How do I correlate Copilot spans with my existing distributed traces?

Implement `onGetTraceContext` with a function that calls `propagation.inject(context.active(), carrier)`. The SDK invokes this before each RPC, injecting `traceparent` and `tracestate` so Copilot spans attach to your current trace.

### Can I change telemetry settings after creating the client?

No. The SDK evaluates `TelemetryConfig` once during `start()` via `buildRuntimeEnv()`. To modify settings, construct a new `CopilotClient` instance with updated configuration.