You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue was organized and drafted with the assistance of AI. All descriptions, reproduction details, and supporting evidence have been manually reviewed and confirmed.
Summary
When using tracing@codex-observability-plugin v0.1.0 with Codex Desktop, a single Codex turn can be exported as two distinct Langfuse traces.
The two traces have:
the same metadata["codex.turn_id"]
the same start time
identical LLM steps, tool calls, token usage, and cost
different trace IDs and observation IDs
This is not a duplicated UI row or two actual model executions. The same turn is ingested twice, which doubles token and cost analytics.
Environment
Codex Desktop / App
Codex CLI/runtime: 0.147.0
Plugin: tracing@codex-observability-plugin0.1.0
Langfuse Cloud US
Node.js: 25.8.1
OS: macOS
Model: gpt-5.6-sol
trace_seed: not configured
Only one Langfuse tracing plugin is installed and enabled
Only one corresponding Stop hook configuration was found
An in-progress trailing turn is still exported, but is intentionally not recorded in the sidecar:
if(turn.completed&&turn.turnId){awaitmarkTurnUploaded(rolloutFile,turn.turnId);}elseif(turn.turnId){// uploaded in-progress turn, but not marked}
This can result in the following sequence:
The first Stop hook invocation reads the rollout.
The final assistant message is present, but task_complete is not.
The plugin exports the turn as in-progress.
The turn is not written to the sidecar because it is not completed.
Codex appends task_complete.
A later hook invocation reads the same completed turn.
The plugin exports the entire turn again with new trace and observation IDs.
Only the second export is recorded in the sidecar.
Expected behavior
For the same:
session_id + codex.turn_id
Langfuse should contain only one finalized trace.
A generation identified by codex.turn_id + codex.step_index should not be ingested more than once, and its token usage and cost should only be counted once.
Suggested fix
One straightforward approach is:
do not export an incomplete trailing turn
wait until the rollout contains task_complete or turn_aborted
export and sidecar-mark the completed turn
skip it during later hook invocations
Another option is to derive stable idempotent IDs for turns, generations, and tool observations from fields such as:
session_id
turn_id
step_index / tool call id
That would prevent a repeated hook invocation from creating new traces and observations for the same turn.
Suggested regression test:
Process a rollout containing task_started but no task_complete.
Assert that no remote spans are emitted and no sidecar entry is written.
Append task_complete to the same rollout.
Process it again and assert that exactly one completed turn is exported.
Process it a third time and assert that the sidecar prevents another export.
Assert that only one trace exists and cost is counted once.
PR #17 describes the same lifecycle issue, but it is still open and unmerged. The issue remains reproducible with Codex Desktop 0.147.0 and plugin v0.1.0.
For privacy, this report does not include prompt text, assistant output, tool inputs or outputs, project identifiers, or Langfuse credentials.
Note
This issue was organized and drafted with the assistance of AI. All descriptions, reproduction details, and supporting evidence have been manually reviewed and confirmed.
Summary
When using
tracing@codex-observability-pluginv0.1.0 with Codex Desktop, a single Codex turn can be exported as two distinct Langfuse traces.The two traces have:
metadata["codex.turn_id"]This is not a duplicated UI row or two actual model executions. The same turn is ingested twice, which doubles token and cost analytics.
Environment
0.147.0tracing@codex-observability-plugin0.1.025.8.1gpt-5.6-soltrace_seed: not configuredStophook configuration was foundObserved behavior
One real Codex turn:
was ingested as two traces:
Both traces contain:
The final LLM generation is identical in both traces:
However, the two generations have different observation IDs:
The full turn cost was also recorded twice:
Local rollout evidence
The local rollout contains only one turn execution:
The two Langfuse root observations have these timestamps:
This shows that:
task_completehad not yet been written when Trace A was exported.task_completetimestamp.task_startedand onetask_complete.turn_idexactly once.Likely root cause
The current code only skips a turn when it is both completed and already present in the sidecar:
An in-progress trailing turn is still exported, but is intentionally not recorded in the sidecar:
This can result in the following sequence:
task_completeis not.task_complete.Expected behavior
For the same:
Langfuse should contain only one finalized trace.
A generation identified by
codex.turn_id + codex.step_indexshould not be ingested more than once, and its token usage and cost should only be counted once.Suggested fix
One straightforward approach is:
task_completeorturn_abortedAnother option is to derive stable idempotent IDs for turns, generations, and tool observations from fields such as:
That would prevent a repeated hook invocation from creating new traces and observations for the same turn.
Suggested regression test:
task_startedbut notask_complete.task_completeto the same rollout.Related
PR #17 describes the same lifecycle issue, but it is still open and unmerged. The issue remains reproducible with Codex Desktop 0.147.0 and plugin v0.1.0.
For privacy, this report does not include prompt text, assistant output, tool inputs or outputs, project identifiers, or Langfuse credentials.