Claude Code
Claude Code has two collection surfaces: native OTLP (metrics, log events, and — on Claude Code ≥ 2.1.x — traces, emitted directly when the right environment variables are set) and session transcripts (the append-only JSONL Claude Code writes per session to ~/.claude/projects/, scraped by the collector’s file_log receiver). Native OTLP is the preferred, better-documented path; transcripts carry richer conversational detail at the cost of an undocumented, drifting schema.
What it collects
- Metrics — session count, lines of code added/removed, PRs, commits, cost and token usage by model, tool-edit decisions, active time.
- Log events —
user_prompt,tool_result,api_request,api_error,tool_decision,hook_execution_complete,mcp_server_connection,subagent_completed,skill_activated,plugin_loaded, and more. - Traces (Claude Code ≥ 2.1.x) — an
interactionspan per prompt-to-response cycle, anllm_requestspan per model call underneath it, and atoolspan per dispatch with its execution and permission-wait children. - Transcripts — every user/assistant/tool message for every session.
What you get
The pack installs a semantic layer over all four sources plus a set of routed dashboards, at @frames/@claude_code/:
| Route | What it answers |
|---|---|
@claude_code | A hub whose sections are routes: Overview (spend, tokens, rhythm), Usage (where the money goes, by model / effort / skill / request class), Work (people, sessions, skills, MCP servers, tools, commits, PRs, lines), Reliability (latency, TTFT, failures, API errors) |
sessions / sessions/{SessionId} | Every session, and one session sectioned into conversation, usage, tools, reliability and a span timeline — each at its own URL |
users / users/{UserEmail} | Everyone with telemetry, and one person’s spend, surfaces, sessions and tools |
tools / tools/{ToolName} | Tool calls, failure rates and durations |
skills / skills/{SkillName} | What a skill dispatches, and the spend attributed to it |
hooks / hooks/{HookName} | Hook runs, duration and how each is registered |
agents / agents/{AgentName} | Sub-agent runs, spend by model, tool uses |
mcp/servers/… | MCP servers, their connection health, and the tools each exposes |
@frames/@claude_code/README.md is the data reference — every stream, scalar and measure, with the coverage figures and caveats behind them. Panels live in *.templates.json files beside each page, so a *.templates.overrides.json retunes one panel without forking the page (see pages and templates). The hub and the session page declare their parts as sections, so each is its own URL and noemata validate --online renders all of them.
What Noemata does automatically
Enabling this integration writes an env block into ~/.claude/settings.json (turning on telemetry, pointing it at Noemata’s collector, and — on Claude Code ≥ 2.1.x — enabling the trace SDK), and configures the collector to receive it. Because ~/.claude/settings.json is outside the repository, this write only happens with integrations.external_edits on; setup asks once and records a decline.
What you still have to do
- Open a new terminal after enabling — Claude Code reads
settings.json’senvblock at startup, so a session already running won’t pick up the change. - Check for a managed-settings file. If your machine has
/Library/Application Support/ClaudeCode/managed-settings.json(macOS) or/etc/claude-code/managed-settings.json(Linux) with its ownenvblock, that overrides Noemata’s edit silently — telemetry won’t flow and nothing will tell you why. - Transcript history follows Claude Code’s own retention (
cleanupPeriodDays, 30 by default). Archive externally if you want a longer window than that. user.emailappears on OAuth-authenticated sessions: Claude Code stamps it on native telemetry, and reconcile stamps it on transcripts from~/.claude.json(withuser.id,user.account_uuid,organization.id), which also puts it in the generated, gitignoredconfig/collector/collector.yml. A re-login lands at the next reconcile. It’s identity rather than conversation content, soredact_contentdoesn’t remove it — scrub it downstream if that’s a concern.
What redact_content does
By default (redact_content: false), the content gates are open: prompts, assistant responses, and tool inputs/results are sent. Setting redact_content: true closes them, in two places:
- At the source — the env vars that gate prompt, response, and tool-content text (
OTEL_LOG_USER_PROMPTS,OTEL_LOG_ASSISTANT_RESPONSES,OTEL_LOG_RAW_API_BODIES, and — on Claude Code ≥ 2.1.x, where tool input/output is emitted as span events —OTEL_LOG_TOOL_CONTENT) are turned off, so that content is never emitted in the first place. - In the collector — everything else (tool inputs and results on versions with no trace SDK, transcript message content) is stripped by an explicit allowlist of attribute keys, applied to every native-OTLP and transcript record. What survives: identity, session structure, timings, token/cost accounting, and tool/skill/MCP-server names; their inputs, their outputs, and the conversation itself are always stripped.
This only holds for a collector Noemata owns. The allowlist is enforced in the collector, so with collect.collector: external — a collector this project doesn’t generate config for — reconcile warns that it can’t apply, and content reaches whatever that collector exports to. The source-level gates above still apply regardless.
Settings
{ "integrations": { "installed": { "claude_code": { "redact_content": false, "hooks": { "validate_frames_on_edit": "offline" } } } }}redact_content— see above. Defaults tofalse.hooks.validate_frames_on_edit—offline(default),online, ornone. Installs aPostToolUsehook that re-validates the frames an edit invalidates. See Validating a frame.
Further reading
- Claude Code monitoring docs — the canonical source for metric and event names.