An opencode plugin that exports telemetry via OpenTelemetry (OTLP over gRPC or HTTP/protobuf), mirroring the same signals as Claude Code's monitoring.
| Metric | Type | Description |
|---|---|---|
opencode.session.count | Counter | Incremented on each session.created event |
opencode.token.usage | Counter | Per token type: input, output, reasoning, cacheRead, cacheCreation |
opencode.cost.usage | Counter | USD cost per completed assistant message |
opencode.lines_of_code.count | Counter | Gross positive churn, not a net total. Emits the positive delta of additions/deletions since the previous session.diff for the same session; negative deltas (when opencode's cumulative additions or deletions shrinks vs. the last event) are dropped. Summing the counter therefore reports gross lines added/removed across forward transitions — it does not reconcile back to the session's current state after any revert (full or partial). Intra-message rewrites that opencode collapses in its per-message cumulative are not visible here at all. Use opencode.lines_of_code.total for the authoritative live cumulative. |
opencode.lines_of_code.total | Gauge | Authoritative live cumulative lines added/removed for the session. Refreshed on every session.diff with opencode's current cumulative value. Drops back to 0 if opencode reports a revert to baseline, and tracks partial reverts faithfully. Query this (not the counter) to answer "what does this session currently amount to". |
opencode.commit.count | Counter | Git commits detected via bash tool |
opencode.tool.duration | Histogram | Tool execution time in milliseconds |
opencode.cache.count | Counter | Cache activity per message: type=cacheRead or type=cacheCreation |
opencode.session.duration | Histogram | Session duration from created to idle in milliseconds |
opencode.message.count | Counter | Completed assistant messages per session |
opencode.session.token.total | Histogram | Total tokens consumed per session, recorded on idle |
opencode.session.cost.total | Histogram | Total cost per session in USD, recorded on idle |
opencode.model.usage | Counter | Messages per model and provider |
opencode.retry.count | Counter | API retries observed via session.status events |
| Event | Description |
|---|---|
session.created | Session started |
session.idle | Session went idle (includes total tokens, cost, messages) |
session.error | Session error |
user_prompt | User sent a message (includes prompt_length, model, agent) |
api_request | Completed assistant message (tokens, cost, duration) |
api_error | Failed assistant message (error summary, duration) |
tool_result | Tool completed or errored (duration, success, output size) |
tool_decision | Permission prompt answered (accept/reject) |
commit | Git commit detected |
Add the plugin to your opencode config at ~/.config/opencode/opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@devtheops/opencode-plugin-otel"]
}Or point directly at a local checkout for development:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["/path/to/opencode-plugin-otel/src/index.ts"]
}All configuration is via environment variables. Set them in your shell profile (~/.zshrc, ~/.bashrc, etc.).
| Variable | Default | Description |
|---|---|---|
OPENCODE_ENABLE_TELEMETRY | (unset) | Set to any non-empty value to enable the plugin |
OPENCODE_OTLP_ENDPOINT | http://localhost:4317 | OTLP collector endpoint. Always include a URL scheme. For grpc, use the collector URL (for example http://localhost:4317 or grpc://collector:4317). For http/protobuf and http/json, use the base URL and the plugin will append /v1/traces, /v1/metrics, and /v1/logs. |
OPENCODE_OTLP_PROTOCOL | grpc | OTLP transport protocol: grpc, http/protobuf, or http/json |
OPENCODE_OTLP_METRICS_INTERVAL | 60000 | Metrics export interval in milliseconds |
OPENCODE_OTLP_LOGS_INTERVAL | 5000 | Logs export interval in milliseconds |
OPENCODE_METRIC_PREFIX | opencode. | Prefix for all metric names (e.g. set to claude_code. for Claude Code dashboard compatibility) |
OPENCODE_DISABLE_METRICS | (unset) | Comma-separated list of metric name suffixes to disable (e.g. cache.count,session.duration) |
OPENCODE_DISABLE_LOGS | (unset) | Set to any non-empty value to suppress all OTLP log events while leaving metrics and traces unchanged |
OPENCODE_DISABLE_TRACES | (unset) | Comma-separated list of trace types to disable (session, llm, tool). Use all, *, true, or 1 to disable every trace type |
OPENCODE_OTLP_HEADERS | (unset) | Comma-separated key=value headers added to all OTLP exports. Keep out of version control — may contain sensitive auth tokens. |
OPENCODE_OTLP_HEADERS_HELPER | (unset) | Executable script/binary that returns dynamic OTLP headers as JSON after an auth failure. Helper headers override OPENCODE_OTLP_HEADERS. |
OPENCODE_RESOURCE_ATTRIBUTES | (unset) | Comma-separated key=value pairs merged into the OTel resource. Example: service.version=1.2.3,deployment.environment=production |
OPENCODE_OTLP_METRICS_TEMPORALITY | (unset) | Metrics aggregation temporality: delta, cumulative, or lowmemory. Required for Datadog (delta). Copied to OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE. |
export OPENCODE_ENABLE_TELEMETRY=1
export OPENCODE_OTLP_ENDPOINT=http://localhost:4317
export OPENCODE_OTLP_PROTOCOL=grpc
opencodeAlways set OPENCODE_OTLP_ENDPOINT to a full URL with a scheme. Scheme-less values like localhost:4317 are rejected.
For OPENCODE_OTLP_PROTOCOL=http/protobuf or OPENCODE_OTLP_PROTOCOL=http/json, set OPENCODE_OTLP_ENDPOINT to the collector base URL rather than a per-signal path. The plugin expands it to /v1/traces, /v1/metrics, and /v1/logs automatically.
# Auth token for a managed collector (e.g. Honeycomb, Grafana Cloud)export OPENCODE_OTLP_HEADERS="x-honeycomb-team=your-api-key,x-honeycomb-dataset=opencode"# Tag every metric and log with deployment contextexport OPENCODE_RESOURCE_ATTRIBUTES="service.version=1.2.3,deployment.environment=production"Security note:
OPENCODE_OTLP_HEADERStypically contains auth tokens. Set it in your shell profile (~/.zshrc,~/.bashrc) or a secrets manager — never commit it to version control or print it in CI logs.
Use OPENCODE_OTLP_HEADERS_HELPER when your collector requires short-lived authentication tokens. When this is set, the plugin prewarms the helper once during startup so the first export can use fresh credentials. If a later OTLP export fails with an authentication error (401/403 for HTTP or UNAUTHENTICATED/PERMISSION_DENIED for gRPC), the plugin refreshes headers again, rebuilds the exporter, and retries the failed export once.
export OPENCODE_OTLP_HEADERS_HELPER=/path/to/opencode-otel-headers.shUse an absolute helper path. If you need the path to follow the current project, OPENCODE_OTLP_HEADERS_HELPER also supports ${PROJECT_ROOT}, ${WORKTREE}, and ${DIRECTORY} placeholders.
export OPENCODE_OTLP_HEADERS_HELPER='${PROJECT_ROOT}/scripts/opencode-otel-headers.sh'The helper must be executable and print a JSON object to stdout:
#!/bin/shprintf'{"Authorization":"Bearer %s"}'"$(get-token.sh)"For a Cloud Run collector using IAM authentication, get-token.sh might be gcloud auth print-identity-token.
If OPENCODE_OTLP_HEADERS is also set, helper-provided headers override static headers with the same name. Header values are never logged.
Use OPENCODE_DISABLE_METRICS to suppress individual metrics. The value is a comma-separated list of metric name suffixes (without the prefix).
Disabling a metric only stops the counter/histogram from being incremented — the corresponding log events are still emitted.
# Disable a single metricexport OPENCODE_DISABLE_METRICS="retry.count"# Disable multiple metricsexport OPENCODE_DISABLE_METRICS="cache.count,session.duration,session.token.total,session.cost.total,model.usage,retry.count,message.count"# Disable the new per-session cumulative gauge while keeping the delta counterexport OPENCODE_DISABLE_METRICS="lines_of_code.total"The following metrics are specific to opencode and have no equivalent in Claude Code's built-in monitoring. If you are using a Claude Code dashboard and want to avoid cluttering it with opencode-only metrics, you can disable them:
export OPENCODE_DISABLE_METRICS="cache.count,session.duration,session.token.total,session.cost.total,model.usage,retry.count,message.count"| Metric suffix | Why it's opencode-only |
|---|---|
cache.count | Tracks cache read/write activity as occurrence counts — not a Claude Code signal |
session.duration | Session wall-clock duration — not emitted by Claude Code |
session.token.total | Per-session token histogram — not emitted by Claude Code |
session.cost.total | Per-session cost histogram — not emitted by Claude Code |
model.usage | Per-model message counter — not emitted by Claude Code |
retry.count | API retry counter — not emitted by Claude Code |
message.count | Completed message counter — not emitted by Claude Code |
Use OPENCODE_DISABLE_LOGS to suppress every OTLP log event emitted by the plugin.
export OPENCODE_DISABLE_LOGS=1This only disables OTLP logs. Metrics and traces continue to be exported unless they are disabled separately.
Use OPENCODE_DISABLE_TRACES to suppress one or more trace types.
# Disable one trace typeexport OPENCODE_DISABLE_TRACES="tool"# Disable multiple trace typesexport OPENCODE_DISABLE_TRACES="llm,tool"# Disable every trace type explicitlyexport OPENCODE_DISABLE_TRACES="all"Accepted explicit "disable all traces" values are all, *, true, and 1.
export OPENCODE_ENABLE_TELEMETRY=1
export OPENCODE_OTLP_ENDPOINT="https://ingest.us.signoz.cloud:443"export OPENCODE_OTLP_HEADERS="signoz-ingestion-key=<SIGNOZ_INGESTION_KEY>"Use
https://ingest.in.signoz.cloud:443for India,https://ingest.eu2.signoz.cloud:443for EU2, etc. See SigNoz setup docs for all regions.
export OPENCODE_ENABLE_TELEMETRY=1
export OPENCODE_OTLP_ENDPOINT=https://otlp.datadoghq.com
export OPENCODE_OTLP_PROTOCOL=http/protobuf
export OPENCODE_OTLP_HEADERS="dd-api-key=YOUR_DATADOG_API_KEY"# Required — Datadog's OTLP intake only accepts delta temporalityexport OPENCODE_OTLP_METRICS_TEMPORALITY=deltaNote: The endpoint is
otlp.datadoghq.com(notapi.datadoghq.com). Useotlp.datadoghq.eufor EU,otlp.us3.datadoghq.comfor US3, etc. See Datadog OTLP docs for all regions.
export OPENCODE_ENABLE_TELEMETRY=1
export OPENCODE_OTLP_ENDPOINT=https://api.honeycomb.io
export OPENCODE_OTLP_PROTOCOL=http/protobufexport OPENCODE_ENABLE_TELEMETRY=1
export OPENCODE_OTLP_ENDPOINT=https://otlp-gateway-prod-us-central-0.grafana.net/otlp
export OPENCODE_OTLP_PROTOCOL=http/protobuf
export OPENCODE_OTLP_HEADERS="Authorization=Basic <base64-instance-id:api-key>"export OPENCODE_METRIC_PREFIX=claude_code.See CONTRIBUTING.md.
This repo includes a reusable workflow at .github/workflows/discord-notify.yml that posts a Discord embed for supported GitHub events. The included .github/workflows/discord-events.yml file wires it up for:
issues.openedpull_request.openedrelease.published
Set an org or repo secret named DISCORD_WEBHOOK and the workflow will post to that webhook automatically.
To reuse it from another repository in the DEVtheOPS org:
name: Discord Eventson:
issues:
types: [opened]pull_request:
types: [opened]release:
types: [published]jobs:
notify-discord:
uses: DEVtheOPS/opencode-plugin-otel/.github/workflows/discord-notify.yml@mainwith:
username: DEVtheOPS Bottitle_prefix: "[DEVtheOPS]"include_body: truesecrets:
discord_webhook: ${{ secrets.DISCORD_WEBHOOK }}Available workflow inputs:
username: webhook display nameavatar_url: webhook avatar image URLtitle_prefix: optional title prefix for the embedinclude_body: include the issue, PR, or release body in the cardcolor: fallback embed color for unsupported events