Overview
Moda ingests first-class telemetry from coding-agent runtimes. The primary signal is OTLP logs, with OTLP traces and metrics accepted on the same provider-prefixed route family. Customers can also POST JSON payloads from custom hook scripts for richer per-event signals. Together these signals power session, tool, skill, and cost analytics on top of Claude Code, Codex, and Cursor without modifying agent code or wrapping the runtime in a custom SDK.Endpoints
| Endpoint | Purpose |
|---|---|
POST /v1/otel/claude/logs | Primary Claude Code runtime event path (OTLP logs). |
POST /v1/otel/claude/traces | Claude Code OTLP trace spans. |
POST /v1/otel/claude/metrics | Claude Code OTLP metrics. |
POST /v1/otel/claude/hooks | Claude Code hook JSON payloads from installed hook scripts. |
POST /v1/otel/codex/logs | Primary Codex runtime event path (OTLP logs). |
POST /v1/otel/codex/traces | Codex OTLP trace spans. |
POST /v1/otel/codex/metrics | Codex OTLP metrics. |
POST /v1/otel/codex/hooks | Codex hook JSON payloads from installed hook scripts. |
POST /v1/otel/cursor/logs | Primary Cursor runtime event path (OTLP logs). |
POST /v1/otel/cursor/traces | Cursor OTLP trace spans. |
POST /v1/otel/cursor/metrics | Cursor OTLP metrics. |
POST /v1/otel/cursor/hooks | Cursor hook JSON payloads from installed hook scripts. |
Authorization: Bearer <MODA_TOKEN>. OTLP routes accept JSON (Content-Type: application/json) or protobuf (Content-Type: application/x-protobuf) bodies. Hook routes accept JSON.
Metric envelopes are accepted and persisted to events_raw (Layer 0) as raw payloads. Normalized rows are NOT yet written to coding_agent_events for metrics — only logs, traces, and hook events produce normalized rows in the first release. Token-usage metrics in particular are stored raw and not yet exposed in coding_agent_events-backed dashboards.
Claude Code setup
Recommended: moda init
The Moda CLI configures Claude Code’s OTLP exporter for you. moda init (or moda hooks install on its own) writes the telemetry environment block — including the endpoints and your Authorization: Bearer header — into the env section of ~/.claude/settings.json, so every Claude Code session exports traces, logs, and metrics without any manual exports.
env block in settings.json), and not from ~/.moda/config.json, this is the step that makes telemetry flow. The CLI resolves your API key (env first, then ~/.moda/config.json) into the auth header and chmods settings.json to 0600 since it now holds that key. Re-running is idempotent and refreshes the key/endpoints in place; other env entries are left untouched.
Manual: export the OTEL variables yourself
For environments where the CLI doesn’t run the agent (CI, a service unit, a container entrypoint), export the standard OpenTelemetry variables in the shell that launches Claude Code:These are the same variables
moda init writes into ~/.claude/settings.json. An exported value in the launching shell wins over the settings.json env block, so an existing shell with stale exports keeps using them until you start a fresh session.OTEL_LOG_USER_PROMPTS is not set in the shell that launches claude, the OTLP prompt attribute is empty or the literal placeholder <REDACTED> while prompt_length still reflects the real size. Moda does not store that placeholder as message text.
When OTEL_LOG_USER_PROMPTS=1, Moda persists the real prompt attribute into coding_agent_events.prompt_text (after secret redaction). You do not need CODING_AGENT_CAPTURE_PROMPTS on the ingest worker for emitter-provided prompt text; that flag is for server-wide capture or hook payloads without a Claude-side opt-in.
OTEL_RESOURCE_ATTRIBUTES is how Moda groups events by org, project, user, and repo. See Resource attributes Moda reads below for the full list.
Because Claude Code defaults to OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf, the logs endpoint must accept protobuf bodies. To smoke-test the spec-mandated protobuf path directly (swap application/x-protobuf for application/json if your exporter is JSON-mode):
Codex setup
moda init or moda hooks install --agent=codex writes the Moda hook config plus the provider-specific OTEL env block into ~/.codex/hooks.json. For managed environments where you configure Codex directly, add the following block to your Codex config:
This config should live in your user-level or managed Codex config, not in a project-local file. Project-local OTEL config can leak between repositories and is harder to audit.
Hooks (optional)
Both Claude Code and Codex support custom hook scripts that fire on session, prompt, tool, and permission events. Moda exposes dedicated JSON endpoints so those hook scripts can POST structured payloads directly, without going through OTLP. This is useful when you want richer per-event signals than the runtime emits over OTLP, for example custom approval decisions, repo metadata, or sandbox details./v1/otel/claude/hooks for /v1/otel/codex/hooks or /v1/otel/cursor/hooks when sending from a Codex or Cursor hook script.
Skill creation (Moda CLI hooks)
Themoda CLI installs coding-agent hooks that capture a per-event shadow-repo snapshot (file state correlated to each prompt/tool call) and signal session ends. This powers Moda’s skill-creation pipeline, which turns repeated corrections into reusable SKILL.md files.
moda init prompts before enabling coding-agent trace ingestion. If accepted, it runs moda hooks install --agent=all so Claude Code, Codex, and Cursor all emit shadow snapshots and get provider-specific OTLP exporter env blocks. Use --no-coding-agent-traces to skip this in setup automation. moda hooks install remains backward-compatible and installs Claude Code hooks by default. Pass --agent=codex, --agent=cursor, or --agent=all to install additional hook adapters. Claude hooks and OTEL env are written to ~/.claude/settings.json; Codex hooks and OTEL env are written to ~/.codex/hooks.json; Cursor hooks and OTEL env are written to ~/.cursor/hooks.json. The CLI chmods these config files to 0600 because the OTLP header includes the resolved API key. Only MODA_API_KEY is needed locally — it is resolved from the environment first, then from ~/.moda/config.json written by moda init, so hooks work regardless of how the agent is launched (login shell, Infisical, etc.). The local machine never holds ClickHouse, model, or sandbox credentials.
On session end/stop, the CLI posts a creds-free session_end signal with the final shadow workspace metadata. Skill creation itself runs server-side when you call moda skills gen: the skill harness processes unhandled coding-agent traces, triggers analytics clustering, distills candidate skills, and promotes candidates that pass the gate.
Pulling generated skills into your project
Promoted skills are served back to your coding agent withmoda skills pull, which fetches your tenant’s skills (authenticated with the same API key) and writes each into the agent’s skill directory so it can use them:
moda init runs this automatically after detecting your coding agent, so existing skills are available immediately on setup. Re-running pull is idempotent: unchanged skills are skipped and updated ones are rewritten. The full SKILL.md text is stored durably server-side (in ClickHouse), so retrieval does not depend on the ephemeral skill-harness container filesystem.
Privacy & data capture
Prompt text and tool output snippets are off by default. Moda stores event-level metadata (tool names, durations, token counts, success/failure, error types) but does not persist user prompts or command output unless you explicitly opt in.Capture policy
The ingest worker exposes three env flags that, together, determine whether a given prompt or output snippet is persisted. The effective rule applied to every event is:
capture = serverCapture OR (allowEventLevelOptIn AND eventLevelOptIn)
| Env var | Default | Purpose |
|---|---|---|
CODING_AGENT_CAPTURE_PROMPTS | false | Server-side switch for prompt text. When true, every event’s prompt text is persisted (after redaction + truncation). |
CODING_AGENT_CAPTURE_OUTPUT_SNIPPETS | false | Server-side switch for tool output snippets. When true, every event’s output snippet is persisted (after redaction + truncation). |
CODING_AGENT_ALLOW_EVENT_LEVEL_OPT_IN | false | Master gate for honoring per-event moda.capture_prompts=true / moda.capture_outputs=true attributes. When false, those attributes are ignored. When true, individual events can upgrade themselves into capture even when the server-level flags are off. |
"true" or "1" as truthy; every other value (including empty, "yes", "on") is treated as false.
Prompt and output capture
The three flags combine to give three useful deployment shapes:prompt_text_redacted=true (or output_snippet_redacted=true) marker on attributes so downstream consumers can distinguish “no text emitted” from “text emitted but suppressed at ingest”.
Secret redaction is applied to commands, error messages, and any captured output snippets before insert. Patterns such as
Authorization, Bearer, api_key, token, password, secret, ANTHROPIC_API_KEY, OPENAI_API_KEY, MODA_TOKEN, and COOKIE are stripped.Provider-specific notes
Claude Code: skill activation is first-class. Claude Code emits first-class skill activation events (
claude_code.skill_activated). Moda treats these as canonical skill_activated rows with skill_name and skill_scope populated, so skill usage analytics work without any extra instrumentation.Codex: skill usage is not first-class today. Codex does not currently expose canonical skill telemetry; skill usage is inferred from tool-call attributes or hook payloads. Moda surfaces these signals where present but does not synthesize first-class
skill_activated rows for Codex.Resource attributes Moda reads
The Moda normalizer recognizes the following attribute names on OTLP resource, log record, or span scopes. Set these viaOTEL_RESOURCE_ATTRIBUTES (Claude Code) or your hook payloads (Codex) to route events to the right org, project, and user in Moda.
- Org:
org_id,org.id,company.id,organization.id - Project:
project_id,project.id,moda.project_id - User ID:
user.id,enduser.id,moda.user_id - User email:
user.email,enduser.email - Repo:
repo - Branch:
branch
tenant_id is always resolved from your Moda API key and cannot be overridden via body attributes.
Troubleshooting
Events not appearing? Query the Layer 0 raw store to see whether requests are reaching the ingest worker:source = 'otlp'covers all/v1/otel/{claude,codex,cursor}/{logs,traces,metrics}requests.source = 'coding_agent_hook'covers/v1/otel/{claude,codex,cursor}/hooksrequests.
Authorization header is set and that the exporter endpoint matches the table in Endpoints. If rows are present but no normalized data appears, inspect raw_event to confirm the payload shape and event names.
For more on the underlying OTLP trace pipeline and other ingestion providers, see the Ingestion overview.