# Viewing Claude Desktop and Codex App Telemetry in Grafana Cloud (June 2026)

> Source: https://www.ymotongpoo.com/blog/20260617-ai-desktop-otel-grafana/


## Introduction

In the [previous article](https://zenn.dev/ymotongpoo/articles/20260616-ai-cli-otel-grafana), I described how to send OpenTelemetry telemetry from the Claude Code CLI and Codex CLI on Linux to Grafana Cloud via Grafana Alloy. This time, I'll summarize what I found when trying to do the same with the GUI apps on macOS (Claude Desktop and the Codex App).

## TL;DR

The Codex App works with roughly the same steps as last time, but Claude Desktop is limited. I couldn't find any OpenTelemetry settings for the GUI chat portion, but the Claude Code CLI launched as a "local code session" picks up the previous article's configuration as-is.

## OTel support in each app

| App | OTel support | Where to configure |
|---|---|---|
| Claude Desktop (chat UI) | No settings available | — |
| Claude Desktop's local code sessions (the embedded Claude Code CLI) | Supported | The `env` block in `~/.claude/settings.json` |
| Codex App (`app-server`) | Supported | `~/.codex/config.toml` (user level only) |

## About Claude Desktop

Claude Desktop's configuration file lives at `~/Library/Application Support/Claude/claude_desktop_config.json`, and its main settings are limited to `mcpServers` (MCP server definitions) and `preferences` (display settings and the like) — there are no OTel-related settings to be found. The official OpenTelemetry configuration docs ([Monitoring usage | Claude Code](https://code.claude.com/docs/en/monitoring-usage)) are written for the CLI tool and contain no setup instructions for the Claude Desktop app.

On the other hand, Claude Desktop has a "local code session" feature that lets you run the Claude Code CLI from within the desktop app. In this mode, the Claude Code CLI starts up and reads the `env` block of `~/.claude/settings.json`, so the OTel environment variables configured in the previous article work as-is. If you use code sessions inside the desktop app, no additional configuration is needed.

## Configuring the Codex App

### Set up OTel in ~/.codex/config.toml

The Codex App runs internally as `codex app-server` and reads the user-level `~/.codex/config.toml`. The exact same `[otel]` section from the previous article's Codex CLI setup works for the app too.

```toml
[otel]
environment = "production"
log_user_prompt = false
exporter = { otlp-http = { endpoint = "http://127.0.0.1:4318/v1/logs", protocol = "binary" } }
metrics_exporter = { otlp-http = { endpoint = "http://127.0.0.1:4318/v1/metrics", protocol = "binary" } }
trace_exporter = { otlp-http = { endpoint = "http://127.0.0.1:4318/v1/traces", protocol = "binary" } }
```

Restart the Codex App after configuring.

### Only the user-level configuration takes effect

Even if you write an `[otel]` section in a project-level `.codex/config.toml`, `app-server` ignores it ([openai/codex#17110](https://github.com/openai/codex/issues/17110)). `app-server` reads only the user-level `~/.codex/config.toml`. Since it shares the config file with the CLI, there's no double bookkeeping, but be aware that putting it in the wrong place silently disables it.

### Mode-specific limitations

As a known limitation, `codex mcp-server` emits no telemetry. There are also reports that `codex exec` emits no metrics ([openai/codex#12913](https://github.com/openai/codex/issues/12913)). The Codex App's normal interactive sessions run as `app-server`, so they are covered. You can verify this with the method in the next section.

## Adding the configuration to Alloy on macOS

If you don't have Alloy installed, install it first. Homebrew is the easy way.

```console
brew install grafana/grafana/alloy
```

On macOS, the configuration for a Homebrew-installed Alloy lives at `/opt/homebrew/etc/alloy/config.alloy`. The path differs from Linux (`/etc/alloy/config.alloy`), but the configuration syntax is the same. The config file is the same as [the one from the previous article](https://zenn.dev/ymotongpoo/articles/20260616-ai-cli-otel-grafana#alloy%E5%81%B4%E3%81%AE%E8%A8%AD%E5%AE%9A). The only change is how the API key is set.

```hcl
otelcol.auth.basic "ai_grafana_cloud" {
    username = "<OTLP_INSTANCE_ID>"
    password = sys.env("GCLOUD_RW_API_KEY")
}
```

`GCLOUD_RW_API_KEY` is already set in `/opt/homebrew/etc/alloy/config.env`. The endpoint zone and OTLP instance ID can be found on Grafana Cloud's Connections page.

Also, since `deltatocumulative` is experimental in Alloy, the startup options need `--stability.level=experimental`. On Linux I put it in `CUSTOM_ARGS` in `/etc/default/alloy`, but with the Homebrew version on macOS you write it to `/opt/homebrew/etc/alloy/extra-args.txt` as follows. The contents of this file are passed straight through as command arguments by the launch script (`alloy-wrapper`).

```
--stability.level=experimental
```

After saving `extra-args.txt`, restart Alloy.

```sh
brew services restart grafana/grafana/alloy
```

## Verifying it works

After some interaction in the Codex App, check that OTLP is being received at Alloy's HTTP endpoint.

```console
$ curl -s http://localhost:12345/metrics | grep otelcol_receiver_accepted
otelcol_receiver_accepted_log_records_total{...} 42
otelcol_receiver_accepted_metric_points_total{...} 8
otelcol_receiver_accepted_spans_total{...} 15
```

If the counters are increasing, Alloy is receiving data. Confirm that the sender-side counters (`otelcol_exporter_sent_*`) are increasing as well. On the Grafana Cloud side, you can check with `gcx`.

```console
$ gcx metrics series '{__name__=~"codex.*"}' --since 30m
```

## Comparing App and CLI metrics with gcx

Even with the same `[otel]` configuration, the App (`codex app-server`) and the CLI (`codex exec`) send different metrics. The `job` label indicates the source, so that's the first way to tell them apart.

```console
$ gcx metrics series '{__name__="codex_turn_e2e_duration_ms_milliseconds_count"}' --since 24h --to now
{"__name__":"codex_turn_e2e_duration_ms_milliseconds_count","app_version":"0.139.0",
 "job":"codex_exec","model":"gpt-5.5","service_name":"codex_exec",...}
```

`job="codex_exec"` is the CLI and `job="codex-app-server"` is the App.

The CLI emits detailed turn-level native metrics such as `codex_turn_*` (turn latency and token usage), `codex_conversation_turn_count_total`, and `codex_websocket_*`. The App emits almost none of these native CLI metrics, but sends traces (spans) instead. Grafana Cloud's metrics generation feature derives `traces_spanmetrics_*` metrics from the spans, so you can observe the per-turn internals in a different form.

```console
$ gcx metrics query 'sum by (span_name) (increase(traces_spanmetrics_calls_total[1h]))'
{"metric":{"span_name":"generateText claude-opus-4-6"},"value":[...,"15"]}
{"metric":{"span_name":"plugin/list"},"value":[...,"57.2"]}
{"metric":{"span_name":"config/read"},"value":[...,"4.4"]}
{"metric":{"span_name":"thread/list"},"value":[...,"1.1"]}
...
```

`generateText claude-opus-4-6` corresponds to the number of actual LLM calls. `plugin/list` shows a high count because the App periodically fetches the plugin list in the background.

You can inspect which metrics each source emits by filtering with `gcx metrics series`.

```console
$ gcx metrics series '{job="codex-app-server"}' --since 24h --to now
$ gcx metrics series '{job="codex_exec"}' --since 24h --to now
```

This is also visible in Grafana's Explore.

![Metrics distinguishable by label](20260617-codex.png)

## Summary

Codex yielded telemetry data surprisingly smoothly, but with Claude Desktop it turned out that session telemetry isn't available. For Codex, the `job` label distinguishes the App from the CLI, which makes filtering easy when querying.

