Add opt-in deterministic trace IDs via LANGFUSE_CODEX_TRACE_SEED#24
Merged
jannikmaierhoefer merged 1 commit intoJul 13, 2026
Merged
Conversation
When trace_seed is set (config file or LANGFUSE_CODEX_TRACE_SEED), the
N-th main-thread turn's trace id is hex(sha256(`${seed}:${N}`)).slice(0, 32)
via the Langfuse SDK's createTraceId, so external harnesses can precompute
trace ids before a codex exec run. Subagent-thread rollouts are scoped as
`${seed}:${threadId}:${N}` to avoid collisions. Unset seed keeps current
behavior; derivation failures fall back to auto-generated ids.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
Adds an opt-in
trace_seedconfig (env:LANGFUSE_CODEX_TRACE_SEED) that makes turn trace IDs deterministic and externally precomputable:createTraceId(${seed}:${N})=hex(sha256("<seed>:<N>")).slice(0, 32)— the thread ID is deliberately excluded so external systems can compute the ID before the run starts.createTraceId(${seed}:${childThreadId}:${N}), scoped by thread ID to avoid collisions. Subagent detection uses theparent_thread_id/thread_source: "subagent"markers Codex writes intosession_meta.Why
External systems (CI harnesses, benchmark runners, dataset-experiment services) that run
codex execheadlessly currently have to poll the Langfuse API to discover the trace a run produced, because they can't know the Codex thread ID up front. With a seed, a harness can precompute the trace ID and e.g. link it to a dataset run item viaPOST /api/public/dataset-run-itemsbefore the run even starts — no polling. The README has a complete shell example.How
config.ts:trace_seedresolved through the existing precedence chain (defaults →~/.codex/langfuse.json→<project>/.codex/langfuse.json→ env).trace.ts: the derived ID is pinned on the root turn span via a placeholder remoteparentSpanContext— the pattern the Langfuse TS SDK documents for custom trace IDs, using the SDK's owncreateTraceIdhelper. Generations, tool spans, and nested subagent observations land in the same trace as before; the sidecar dedup is untouched. Turn numbering runs over the full parsed rollout, so IDs stay stable across hook re-invocations even when earlier turns are skipped by dedup.LANGFUSE_CODEX_FAIL_ON_ERRORrethrows for testing).Tests
node:cryptosha256 implementation (not the SDK helper).trace-seed-failure.test.tsmockscreateTraceIdto throw: upload still happens with an auto-generated ID;fail_on_error: truepropagates.dist/index.mjsas the real Stop hook against a local fake Langfuse ingestion server — the uploaded OTLP payload contained exactly the externally precomputed ID for<seed>:1, and the README'sshasumone-liner matchescreateTraceIdbyte-for-byte.Notes for reviewers
0123456789abcdef, never a real span). Langfuse still renders the turn as the trace root; this is the documented SDK pattern for custom trace IDs. Without a seed, spans are exported exactly as before.parent_thread_id/thread_sourceinsession_meta, mirroring what Codex actually writes (SessionMetaincodex-rs/protocol).dist/index.mjsis rebuilt and committed per the repo's bundle policy (pnpm run lintpasses).🤖 Generated with Claude Code