Skip to content

Plugin not sending traces: 3 root causes identified (missing dep, tracer timing, Bun OTEL compat) #7

Description

@eeeming

Summary

After deep debugging (source-level tracing through both OpenCode's plugin loader and the plugin's own code), I identified 3 bugs that prevent @langfuse/opencode-observability-plugin from sending traces to Langfuse.

All testing done with OpenCode v1.17.13 on macOS (Bun runtime), Langfuse v3.205.1 self-hosted.


Bug #1: Missing fast-check transitive dependency

File: Plugin's node_modules after Npm.add() installation

Root cause: effect@3.21.4 depends on fast-check (peer/optional), but OpenCode's Npm.add() uses @npmcli/arborist with ignoreScripts: true, which skips optional/peer dependencies.

Symptom: import() of the plugin module fails with:

Cannot find package 'fast-check' from '.../effect/dist/esm/FastCheck.js'

Fix: Add fast-check as an explicit dependency in package.json, or ensure it's installed:

npm install fast-check --prefix ~/.cache/opencode/packages/@langfuse/opencode-observability-plugin@latest

Bug #2: Tracer created before SDK starts

File: src/langfuse.tscreateLangfuseClient()

Root cause: trace.getTracer() is called before sdk.start(). At that point, no global TracerProvider is registered, so ProxyTracer._getDelegate() returns NoopTracer. All subsequent startSpan() calls produce no-op spans that never reach the processor.

// ❌ Current code
const traceState = {
    tracer: trace.getTracer(tracerName, PLUGIN_VERSION),  // ← BEFORE sdk.start()
    ...
};
const sdk = new NodeSDK({ spanProcessors: [...] });
yield* Effect.sync(() => sdk.start());  // ← AFTER

Fix: Create the tracer AFTER sdk.start():

const traceState = { tracer: null, ... };
const sdk = new NodeSDK({ spanProcessors: [...] });
yield* Effect.sync(() => sdk.start());
traceState.tracer = trace.getTracer(tracerName, PLUGIN_VERSION);  // ← AFTER sdk.start()

Bug #3: Bun runtime — NodeSDK.start() global provider registration not recognized

Root cause: Even with Bug #2 fixed, @opentelemetry/api's global trace.getTracer()returns aProxyTracerthat delegates to a no-op provider. This appears to be a Bun-specific issue whereNodeSDK.start()callstrace.setGlobalTracerProvider()but the global registry isn't properly shared between@opentelemetry/sdk-nodeand the separately-resolved@opentelemetry/apiin the plugin'snode_modules`.

Fix: Bypass the global API entirely. Use NodeTracerProvider directly with provider.getTracer():

// ❌ Before
import { NodeSDK } from "@opentelemetry/sdk-node";
const sdk = new NodeSDK({ spanProcessors: [...] });
sdk.start();
const tracer = trace.getTracer(name, version);

// ✅ After
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
const provider = new NodeTracerProvider({ spanProcessors: [...] });
provider.register();
const tracer = provider.getTracer(name, version);

Verification

After all three fixes, traces appear correctly in Langfuse:

scope: opencode-langfuse-plugin v0.1.0
sessionId: ses_0c80adcb8ffe97...
environment: development
observations: 3

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions