hermes - 💡(How to fix) Fix bug: Langfuse plugin reuses same trace ID for all turns in a session

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Root Cause

The trace key is deterministic and session-scoped:

# Line 222-227
def _trace_key(task_id: str, session_id: str) -> str:
    if task_id:
        return task_id
    if session_id:
        return f"session:{session_id}"  # ← same for ALL turns in session
    return f"thread:{threading.get_ident()}"

And the trace ID is deterministically derived from this key:

# Line 544
trace_id = client.create_trace_id(seed=f"{session_id or 'sessionless'}::{task_id or task_key}")

In on_pre_llm_request (line 729+), the _TRACE_STATE dict caches the trace by task_key. Once a trace exists for a session, all subsequent turns hit the same cache entry and reuse the same root trace:

task_key = _trace_key(task_id, session_id)
with _STATE_LOCK:
    state = _TRACE_STATE.get(task_key)
    if state is None:
        state = _start_root_trace(...)
        _TRACE_STATE[task_key] = state

Code Example

# Line 222-227
def _trace_key(task_id: str, session_id: str) -> str:
    if task_id:
        return task_id
    if session_id:
        return f"session:{session_id}"  # ← same for ALL turns in session
    return f"thread:{threading.get_ident()}"

---

# Line 544
trace_id = client.create_trace_id(seed=f"{session_id or 'sessionless'}::{task_id or task_key}")

---

task_key = _trace_key(task_id, session_id)
with _STATE_LOCK:
    state = _TRACE_STATE.get(task_key)
    if state is None:
        state = _start_root_trace(...)
        _TRACE_STATE[task_key] = state

---

2026-05-25 14:30:30 INFO started trace 82e31d4c11b351b363df0372c2d8f024 for 20260525_100629_ba5465
2026-05-25 14:31:27 INFO started trace 82e31d4c11b351b363df0372c2d8f024 for 20260525_100629_ba5465
2026-05-25 14:35:23 INFO started trace 82e31d4c11b351b363df0372c2d8f024 for 20260525_100629_ba5465
... (repeated 15+ times across the session)
RAW_BUFFERClick to expand / collapse

Problem

The Langfuse observability plugin (plugins/observability/langfuse/__init__.py) generates the same trace ID for every turn in a session, causing all turns to overwrite a single trace instead of creating independent traces per turn.

Root Cause

The trace key is deterministic and session-scoped:

# Line 222-227
def _trace_key(task_id: str, session_id: str) -> str:
    if task_id:
        return task_id
    if session_id:
        return f"session:{session_id}"  # ← same for ALL turns in session
    return f"thread:{threading.get_ident()}"

And the trace ID is deterministically derived from this key:

# Line 544
trace_id = client.create_trace_id(seed=f"{session_id or 'sessionless'}::{task_id or task_key}")

In on_pre_llm_request (line 729+), the _TRACE_STATE dict caches the trace by task_key. Once a trace exists for a session, all subsequent turns hit the same cache entry and reuse the same root trace:

task_key = _trace_key(task_id, session_id)
with _STATE_LOCK:
    state = _TRACE_STATE.get(task_key)
    if state is None:
        state = _start_root_trace(...)
        _TRACE_STATE[task_key] = state

Affected Modes

Both CLI and TUI/Gateway are affected.

  • CLI mode: task_id is empty, _trace_key returns "session:{session_id}" — same for all turns.
  • TUI/Gateway mode: Gateway passes task_id=session_id (line 16914 in gateway/run.py), so _trace_key returns session_id — also same for all turns.

Only Kanban subtasks (unique task_id per task) and delegate_task subagents (unique task_id like sa-0-xxx) produce independent traces.

Evidence

Langfuse logs confirm the same trace ID being reused:

2026-05-25 14:30:30 INFO started trace 82e31d4c11b351b363df0372c2d8f024 for 20260525_100629_ba5465
2026-05-25 14:31:27 INFO started trace 82e31d4c11b351b363df0372c2d8f024 for 20260525_100629_ba5465
2026-05-25 14:35:23 INFO started trace 82e31d4c11b351b363df0372c2d8f024 for 20260525_100629_ba5465
... (repeated 15+ times across the session)

In the Langfuse UI, this session shows only 2 traces for a conversation that had 15+ turns. Earlier turns' data is overwritten by later turns.

Suggested Fix

Option A: New trace per turn — In on_pre_llm_request, finish the existing trace before starting a new one when a new turn is detected (e.g., when api_call_count == 0 and a trace already exists).

Option B: Include turn counter in trace key — Pass api_call_count or a turn UUID into _trace_key so each turn gets a unique key.

Option A is simpler and aligns with how Langfuse sessions work (multiple traces grouped by session_id).

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING