feat(tracing): attach Codex turns to external parent spans - #72
Open
zenor0 wants to merge 2 commits into
Open
Conversation
Accept LANGFUSE_CODEX_TRACEPARENT to attach the complete Codex observation tree to a process-scoped W3C parent span. Honor upstream sampling and preserve application trace ownership. Preserve standalone sampling and deterministic trace IDs. Reuse the existing fixtures with a real Langfuse processor for local regression coverage. Keep npm scripts unchanged. Validation: 51 local tests, full lint, and an npm pack dry run pass. Generated bundles remain untracked.
Add an opt-in credentialed test for a real application span and bundled Codex hook, with a dedicated Vitest config, test:e2e script, and usage documentation. This commit can be dropped without affecting the tracing feature or regular test suite. Validation: 51 local tests, full lint, and an npm pack dry run pass. The real-backend E2E test was not run.
zenor0
marked this pull request as ready for review
September 6, 2026 06:18
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.
Motivation
When an application launches Codex as one step in an existing agent run, the plugin currently exports each Codex turn as a separate Langfuse trace. The application-level work and the plugin's detailed Codex observations therefore appear separately, even though they belong to the same operation.
This PR adds an opt-in attached mode: the launcher passes a W3C
traceparent, and the plugin attaches its existing observation tree beneath that application span. It retains the rollout-derived prompts, outputs, reasoning, model usage, tool results, errors, and subagent observations.trace_seedremains useful for predictable IDs, but its synthetic parent does not establish a parent-child relationship with a real application span. This change supplies that relationship explicitly.Usage And Result
With tracing already enabled, a launcher can pass the context of a sampled parent span when starting its dedicated Codex process:
The resulting observations share the application's trace ID:
Every top-level Codex turn uses the supplied application span as its parent. Generations, tools, and subagents retain their existing nesting. The launcher and plugin must export to the same Langfuse project to see the combined trace.
Design Decisions
Use W3C
traceparentfor the external context. It carries the trace ID, parent span ID, and sampling flags together. Parsing reuses OpenTelemetry'sparseTraceParent, and the resulting span context is marked remote before being passed through the existing conversion path.Bind context to a dedicated process. The intended lifecycle is one application run owning one Codex CLI or App Server process. A process-scoped environment variable fits that lifecycle and keeps the implementation within this plugin. A long-lived App Server shared by unrelated runs would need request-level context propagation into the Stop hook; this PR does not implement that routing. Previously processed turns are still governed by the existing sidecar and are not moved to another trace when the parent changes.
Keep parent context transient and explicitly scoped.
LANGFUSE_CODEX_TRACEPARENTis read only from the hook process environment. It is not alangfuse.jsonsetting and should not be persisted in a shell profile, where unrelated runs could inherit it. The plugin does not consume unscopedTRACEPARENTor propagatetracestate.Leave trace ownership with the launching application. In attached mode, the plugin does not propagate its configured trace name, session, user, tags, or metadata. Codex-specific observation metadata is still emitted. The conversion also carries the application's Langfuse trace-ID claim in the active context, preventing the real
LangfuseSpanProcessorfrom marking the cross-process Codex span as another application root.Honor the parent's sampling decision. Attached mode uses a parent-based sampler so the upstream sampled bit is authoritative, including when
OTEL_TRACES_SAMPLERis set. Standalone mode leaves sampler selection to the existing OpenTelemetry configuration.The ownership integration uses
setLangfuseTraceIdInBaggagefrom@langfuse/core, which is already a transitive dependency and is now declared directly. This helper is exported but marked@internalby the SDK; that dependency is a specific point for maintainer review. Tests use the real Langfuse processor with an in-memory exporter to verify that attached observations are not application roots, while standalone observations remain roots.Behavior
trace_seedand sampler environment configuration.trace_seedLANGFUSE_CODEX_FAIL_ON_ERROR=true, reject the invalid value instead.The invalid-input behavior follows the hook's existing fail-open policy so tracing configuration errors do not block a Codex run by default.
Commit Structure
The first commit contains the feature and its local regression tests and is independently usable. The second commit adds optional backend E2E coverage and the
test:e2escript; it can be dropped without affecting the feature or regular test suite.Validation
The following local checks passed both on the standalone feature commit and with the optional E2E commit applied:
pnpm test: 51 tests passed. Coverage includes parentage across multiple turns and subagents, seed precedence, parsing and failure behavior, sampling, and SDK application-root ownership.pnpm run lint: formatting, TypeScript checks, and build passed.pnpm exec npm pack ./plugins/tracing --dry-run --json: the package includes the generated hook. Following the current upstream contribution workflow, nodistartifacts are committed.Ownership checks share the existing trace fixtures and real processor setup rather than a separate test harness. The opt-in E2E uses the installed Langfuse API client and Node/Vitest utilities for process execution, polling, and cleanup.
The PR also adds an opt-in
pnpm run test:e2etest. It creates a real application span, runs the bundled hook against a fixture, checks the persisted parent-child tree through the Langfuse observations API, and deletes the temporary trace. It requires Langfuse credentials and was not run for this final commit; the regular test suite makes no requests to Langfuse.Opening as a draft for feedback on the process-scoped propagation contract and the SDK trace-ownership integration.