feat(dsh-langfuse)!: migrate to the Langfuse JS SDK v5 - #5
Merged
Conversation
Drop the deprecated `langfuse` v3 peer for the v5 OTEL-based stack: `@langfuse/tracing` + `@langfuse/otel` + `@opentelemetry/sdk-trace-node` (latest: 5.10.1 / 5.10.1 / 2.10.0; optional peers, lazy dynamic import, isolated tracer provider — the process-global OTEL provider stays untouched). v5's observations-first model reshapes the reporter: - a trace IS its root span, created with a forced-fresh traceId (invalid parentSpanContext) so ambient host OTEL spans can't adopt turn traces; trace IO/metadata live on the root observation (replaces trace.update) - spans only export on end: turn/end ends the root, stale roots end on turn/start replace, abandoned roots end on session/disposed, the fiber unload sweep ends everything still tracked, and session-less one-off roots are ended by the call itself - the correlating session.id attribute is stamped on every observation via a handle-keyed WeakMap (the explicit-tree equivalent of propagateAttributes, which can't wrap an event-driven lifecycle) - renames ride otelSpan.updateName (v5 attributes have no name); modelParameters narrow to string|number (stop list serializes) Integration harness: the fake endpoint becomes OTLP/HTTP JSON at /api/public/otel/v1/traces, evaluators parent at the dsh-turn root span (v5: no parentObservationId == null for trace children), and the peer install set lists all five runtime packages. BREAKING CHANGE: consumers must replace the `langfuse` package with the @langfuse/* + @opentelemetry/* peer set (see the package README).
…rvers On a self-hosted Langfuse v3.172.1 the migrated traces rendered as "Unnamed trace" with empty input/output: the v3 server's OTEL ingestion derives the trace row from trace-level span attributes (langfuse.trace.name / langfuse.trace.input / langfuse.trace.output), and per the v4 migration doc such context must be copied to every span — only session.id was stamped per-observation, so the trace row kept the session but lost the name and IO. - stamp langfuse.trace.name + session.id on EVERY observation (WeakMap handle-tree inheritance, replacing the sessionIds map) - trace roots additionally carry langfuse.trace.input/output via the SDK's deprecated-but-official setTraceIO compat path, gated by a roots WeakSet so nested spans never get trace IO; redaction paths stay clean - tests: mock setTraceIO, trace-name inheritance assertions, redaction and non-root negative guards
The nobody containment from #4 never actually ran on CI: pull_request_target evaluates the BASE branch's workflow, so PR #4's own runs used the pre-PR version and the first real execution was PR #5 — which failed at dsh boot: EACCES mkdir $DSH_HOME/profiles/node_modules and EACCES reading pr-head/.env. The runner's ancestor dirs (/home/runner, work/, the checkout parents) are not world-searchable, so the nobody process cannot traverse into its own DSH_HOME or the review checkout. chmod o+x the directory chain: search-without-listing, so the containment boundary (no secret readable from the model process) is unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Migrate
dsh-langfuseoff the deprecatedlangfusev3 package onto the Langfuse JS SDK v5 (all latest):@langfuse/tracing@5.10.1+@langfuse/otel@5.10.1+@opentelemetry/sdk-trace-node@2.10.0as optional peers (the OTEL api/exporter packages@langfuse/otelitself peers on are documented in the README and installed explicitly in CI). Follows the v4→v5 upgrade path.Key design points
parentSpanContextliteral — avoids eagerly loading@opentelemetry/api), so ambient host OTEL spans (e.g. an instrumented HTTP server around dsh) can never adopt turn traces. Trace IO/metadata live on the root observation.turn/endends the root, stale roots end onturn/startreplace, abandoned roots end onsession/disposed, the fiber-unload sweep ends everything still tracked, and session-less one-off roots are ended by the call itself (a v3→v5 regression caught in review: v3 flushedtrace-createeagerly, v5 would have dropped those observations silently).session.idon every observation via a handle-keyed WeakMap — the explicit-tree equivalent of v5's context-scopedpropagateAttributes(), which can't wrap this plugin's event-driven lifecycle.setLangfuseTracerProvider, neverprovider.register()); the host keeps its own OTEL pipeline. Released on unload (finally).otelSpan.updateName()(v5 attributes have noname);modelParametersnarrow tostring | number(stop list serializes).[dsh-langfuse]prefix.Integration harness
The secrets-free fake endpoint is now an OTLP/HTTP JSON sink at
/api/public/otel/v1/traces(the v5 exporter sendsapplication/json), folding OTLP spans into the same observation shape the evaluators share with the real-mode v1 Observations API. Evaluators parent at thedsh-turnroot span (in v5 trace children have a realparentObservationId). The peer install leg lists all five runtime packages.Verification
pnpm typecheck/pnpm build/pnpm test(56/56) /pnpm lint— all greenLANGFUSE_*secrets) runs in CI Stage B; the fake-OTLP leg covers the same evaluators without secretsBREAKING CHANGE: consumers replace the
langfusepackage with the@langfuse/*+@opentelemetry/*peer set — see the package README install section.