Skip to content

fix(langchain): pass trace_name to propagate_attributes in on_chain_start#1610

Open
STHITAPRAJNAS wants to merge 1 commit intolangfuse:mainfrom
STHITAPRAJNAS:fix/on-chain-start-trace-name-propagation
Open

fix(langchain): pass trace_name to propagate_attributes in on_chain_start#1610
STHITAPRAJNAS wants to merge 1 commit intolangfuse:mainfrom
STHITAPRAJNAS:fix/on-chain-start-trace-name-propagation

Conversation

@STHITAPRAJNAS
Copy link
Copy Markdown

@STHITAPRAJNAS STHITAPRAJNAS commented Apr 4, 2026

Problem

When CallbackHandler.on_chain_start fires at the root of a LangChain/LangGraph run (parent_run_id is None), it calls propagate_attributes() without setting trace_name. This means the trace name is determined by whichever internal node's on_chain_start happens to fire first — which is non-deterministic on LangGraph resume.

On initial run the first event usually comes from the root graph, so the name is correct. On resume (e.g. after Command(resume=...) following a human-in-the-loop interrupt), the first on_chain_start can fire from an internal subgraph whose name is "", producing a blank trace name.

The propagate_attributes() API already accepts trace_name — it just wasn't being used.

Fixes #1602.

Changes

langfuse/langchain/CallbackHandler.py

  • on_chain_start — passes trace_name=parsed_trace_attributes.get("trace_name") or span_name to propagate_attributes(). span_name is already computed from serialized + kwargs["name"] (same value used as the span name), so this pins the trace name to the root chain name regardless of which internal node fires first on resume.

  • _parse_langfuse_trace_attributes — adds support for a langfuse_trace_name key in LangChain metadata, following the same pattern as langfuse_session_id / langfuse_user_id / langfuse_tags. When set, it takes priority over the computed span_name.

  • _strip_langfuse_keys_from_dict — adds langfuse_trace_name to the list of keys stripped from observation metadata (consistent with how the other langfuse_* trace-attribute keys are handled).

Tests

Added tests/test_langchain_callback_unit.py — 12 unit tests covering:

  • _parse_langfuse_trace_attributes extracts / ignores / falls back on langfuse_trace_name
  • on_chain_start calls propagate_attributes with the correct trace_name (from serialized, from kwargs, from metadata override)
  • propagate_attributes is NOT called for child runs
  • _strip_langfuse_keys_from_dict strips langfuse_trace_name correctly

All tests use unittest.mock — no API keys or network calls required.

tests/test_langchain_callback_unit.py::TestParseLangfuseTraceAttributes::test_extracts_trace_name_from_metadata PASSED
tests/test_langchain_callback_unit.py::TestParseLangfuseTraceAttributes::test_ignores_non_string_trace_name PASSED
tests/test_langchain_callback_unit.py::TestParseLangfuseTraceAttributes::test_does_not_set_trace_name_when_absent PASSED
tests/test_langchain_callback_unit.py::TestParseLangfuseTraceAttributes::test_extracts_all_attributes_together PASSED
tests/test_langchain_callback_unit.py::TestOnChainStartTraceNamePropagation::test_trace_name_passed_to_propagate_attributes PASSED
tests/test_langchain_callback_unit.py::TestOnChainStartTraceNamePropagation::test_trace_name_uses_kwargs_name_over_serialized PASSED
tests/test_langchain_callback_unit.py::TestOnChainStartTraceNamePropagation::test_metadata_langfuse_trace_name_overrides_span_name PASSED
tests/test_langchain_callback_unit.py::TestOnChainStartTraceNamePropagation::test_propagate_attributes_not_called_for_child_runs PASSED
tests/test_langchain_callback_unit.py::TestOnChainStartTraceNamePropagation::test_empty_span_name_still_propagated PASSED
tests/test_langchain_callback_unit.py::TestStripLangfuseKeys::test_strips_langfuse_trace_name PASSED
tests/test_langchain_callback_unit.py::TestStripLangfuseKeys::test_keeps_langfuse_trace_name_when_flag_set PASSED
tests/test_langchain_callback_unit.py::TestStripLangfuseKeys::test_strips_all_trace_attribute_keys_together PASSED

Disclaimer: Experimental PR review

Greptile Summary

This PR fixes a non-deterministic trace naming bug in the LangChain CallbackHandler: on LangGraph resume, on_chain_start was firing from internal subgraphs before the root, causing propagate_attributes() to be called without a trace_name and picking up a blank or wrong name. The fix pins trace_name to the root chain's span_name (with a new langfuse_trace_name metadata key for user overrides) and adds langfuse_trace_name to the stripped-keys list so it does not leak into span metadata.

  • _parse_langfuse_trace_attributes: Extracts langfuse_trace_name string from metadata, following the same pattern as langfuse_session_id/langfuse_user_id.
  • on_chain_start: Passes trace_name=parsed_trace_attributes.get("trace_name") or span_name to propagate_attributes(), correctly prioritising an explicit metadata override over the computed span name.
  • _strip_langfuse_keys_from_dict: Adds langfuse_trace_name to the list of trace-attribute keys removed from span metadata.
  • New test file: 12 unit tests covering parse, propagation, override, child-run exclusion, and strip behaviour — all run without network calls.

Confidence Score: 5/5

Safe to merge — the fix is targeted, logically correct, and well-covered by tests.

All findings are P2 style issues (unused imports, misleading docstring). No logic, correctness, or security issues found. The core fix correctly uses the existing trace_name parameter of propagate_attributes, and the fallback expression (parsed_trace_attributes.get("trace_name") or span_name) is safe because get_langchain_run_name always returns at least "" — it never produces an empty string.

No files require special attention; the two unused imports in the test file are cosmetic only.

Important Files Changed

Filename Overview
langfuse/langchain/CallbackHandler.py Correctly pins trace_name to the root chain's span_name in propagate_attributes and adds langfuse_trace_name metadata support; no logic issues found.
tests/test_langchain_callback_unit.py Comprehensive unit tests for the fix; has two unused imports (call, LangchainCallbackHandler) and one misleading test docstring.

Sequence Diagram

sequenceDiagram
    participant LC as LangChain/LangGraph
    participant CH as CallbackHandler
    participant PA as propagate_attributes

    LC->>CH: on_chain_start(serialized, inputs, parent_run_id=None)
    CH->>CH: span_name = get_langchain_run_name(serialized, **kwargs)
    CH->>CH: _parse_langfuse_trace_attributes(metadata, tags)
    Note over CH: Extracts langfuse_trace_name (if present & string)
    CH->>PA: propagate_attributes(trace_name = metadata_trace_name OR span_name, ...)
    Note over PA: trace_name is now deterministic —
    Note over PA: always the root chain's name
    PA-->>CH: context manager entered
    CH->>LC: span started

    Note over LC,CH: On LangGraph resume (before fix)
    LC->>CH: on_chain_start(internal_subgraph, parent_run_id=None)
    CH->>PA: propagate_attributes(trace_name=NOT_SET)
    Note over PA: ⚠ blank/wrong trace name

    Note over LC,CH: On LangGraph resume (after fix)
    LC->>CH: on_chain_start(internal_subgraph, parent_run_id=None)
    CH->>CH: span_name = get_langchain_run_name(serialized) → root graph name
    CH->>PA: propagate_attributes(trace_name=span_name)
    Note over PA: ✅ trace name is always pinned
Loading

Reviews (1): Last reviewed commit: "fix(langchain): pass trace_name to propa..." | Re-trigger Greptile

(2/5) Greptile learns from your feedback when you react with thumbs up/down!

…tart

When CallbackHandler.on_chain_start fires at the root of a chain
(parent_run_id is None), propagate_attributes was called without a
trace_name, so the trace name was determined by whichever internal node's
on_chain_start happened to fire first. On LangGraph resume (e.g. after a
human-in-the-loop interrupt) that node is often an internal subgraph whose
name is "", which produces a blank trace name.

The fix passes span_name — the name already computed from the serialized
runnable and kwargs — as trace_name to propagate_attributes. This ensures
the trace name is always pinned to the root chain's name regardless of
execution order on resume.

As a companion change, _parse_langfuse_trace_attributes now also reads a
langfuse_trace_name key from LangChain metadata, consistent with the
existing langfuse_session_id / langfuse_user_id / langfuse_tags pattern.
When present, metadata langfuse_trace_name takes priority over the
computed span_name. The key is also added to the strip-list in
_strip_langfuse_keys_from_dict so it does not leak into observation
metadata.

Fixes langfuse#1602
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 4, 2026

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CallbackHandler.on_chain_start does not pass trace_name to propagate_attributes, causing non-deterministic trace names on LangGraph resume

2 participants