Instructions for AI agents working in this repository.
Always run after making changes:
bun run typecheckThere is no build step. TypeScript source files are published directly and loaded natively by Bun.
bun testsrc/
├── index.ts — Plugin entrypoint
├── types.ts — Shared types
├── config.ts — Env config and log level
├── otel.ts — OTel SDK setup and instruments
├── probe.ts — OTLP endpoint TCP probe
├── util.ts — errorSummary, setBoundedMap
└── handlers/
├── session.ts — Session lifecycle events
├── message.ts — LLM message and tool part events
├── permission.ts — Tool permission events
└── activity.ts — File diffs and git commits
- Bun over Node — use
bun,bun test,bun run. Never usenode,npx,jest, orvitest. - No comments unless explicitly requested.
- No
sdk-node— the OTel Node SDK meta-package is intentionally excluded; use individual packages. HandlerContext— all event handlers receive aHandlerContext(defined insrc/types.ts). Do not importclientor OTel globals directly inside handlers; thread them through the context.setBoundedMap— always use this instead ofMap.setforpendingToolSpansandpendingPermissionsto prevent unbounded growth.- Single source of truth for tokens/cost — token and cost counters are incremented only in
message.updated(src/handlers/message.ts), never instep-finish. - Shutdown — OTel providers are flushed via
SIGTERM/SIGINT/beforeExit. Do not useprocess.on("exit")for async flushing. - All env vars are
OPENCODE_prefixed —OPENCODE_ENABLE_TELEMETRY,OPENCODE_OTLP_ENDPOINT,OPENCODE_OTLP_METRICS_INTERVAL,OPENCODE_OTLP_LOGS_INTERVAL,OPENCODE_METRIC_PREFIX,OPENCODE_OTLP_HEADERS,OPENCODE_RESOURCE_ATTRIBUTES,OPENCODE_SPAN_ATTRIBUTES,OPENCODE_METRIC_ATTRIBUTES,OPENCODE_EXCLUDE_METRICS_ATTRIBUTES,OPENCODE_COST_USAGE_SCALE. Never use bareOTEL_*names for plugin config.loadConfigcopiesOPENCODE_OTLP_HEADERS→OTEL_EXPORTER_OTLP_HEADERSandOPENCODE_RESOURCE_ATTRIBUTES→OTEL_RESOURCE_ATTRIBUTESbefore the SDK initializes. - Attribute channels are per-signal and independent — all parsed with
parseKeyValueAttributes(src/config.ts):OPENCODE_RESOURCE_ATTRIBUTES→ OTelResource(producer metadata), applied inbuildResource(src/otel.ts).OPENCODE_SPAN_ATTRIBUTES→ spans only, threaded asctx.spanAttributesand spread into eachtracer.startSpanattribute block (session/llm/tool). Not incommonAttrs, so it never reaches logs or metrics.OPENCODE_METRIC_ATTRIBUTES→ metrics only, merged via instrument wrapping increateInstruments(src/otel.ts); never touches spans or logs.OPENCODE_EXCLUDE_METRICS_ATTRIBUTES→ metrics only, comma-separated attribute keys stripped from each metric data point increateInstruments(e.g.session.id); applied last so it always wins overOPENCODE_METRIC_ATTRIBUTES. Spans and logs are unaffected.commonAttrs(currently justproject.id) is the only set applied to all three signals.
OPENCODE_ENABLE_TELEMETRY— all OTel instrumentation is gated on this env var. The plugin always loads regardless; only telemetry is disabled when unset.OPENCODE_METRIC_PREFIX— defaults toopencode.; set toclaude_code.for Claude Code dashboard compatibility.OPENCODE_COST_USAGE_SCALE— positive number (default1); multiplier applied only to thecost.usagecounter. Workaround for backends that round metric values to one decimal place server-side (e.g. AppSignal). Thesession.cost.totalhistogram, spans (gen_ai.usage.cost,cost_usdattributes), and log events all keep raw USD. Threaded throughHandlerContext.costUsageScale; when ≠ 1, thecost.usagemetric unit is reported asUSD/<scale>and its description is annotated.
All commits must follow Conventional Commits:
<type>[optional scope]: <description>
Common types: feat, fix, perf, refactor, test, docs, ci, chore, build.
Use ! or a BREAKING CHANGE: footer for breaking changes.
Examples:
feat(handlers): add support for file.edited event
fix(probe): handle malformed endpoint URL without throwing
chore(deps): bump @opentelemetry/api to 1.10.0