Skip to content

fix(langchain): propagate run name as trace name#1743

Open
sjsjsjjs534 wants to merge 1 commit into
langfuse:mainfrom
sjsjsjjs534:codex/propagate-langchain-trace-name
Open

fix(langchain): propagate run name as trace name#1743
sjsjsjjs534 wants to merge 1 commit into
langfuse:mainfrom
sjsjsjjs534:codex/propagate-langchain-trace-name

Conversation

@sjsjsjjs534

@sjsjsjjs534 sjsjsjjs534 commented Jul 8, 2026

Copy link
Copy Markdown

Summary

  • use the LangChain root run name as the default propagated trace name
  • preserve explicit langfuse_trace_name metadata as the override when provided
  • add a regression test for the default trace-name propagation path

Fixes #1602

Tests

  • PYTHONDONTWRITEBYTECODE=1 UV_CACHE_DIR=/private/tmp/uv-cache-langfuse-python uv run --frozen pytest tests/unit/test_langchain.py -q
  • UV_CACHE_DIR=/private/tmp/uv-cache-langfuse-python uv run --frozen ruff check langfuse/langchain/CallbackHandler.py tests/unit/test_langchain.py
  • UV_CACHE_DIR=/private/tmp/uv-cache-langfuse-python uv run --frozen ruff format --check langfuse/langchain/CallbackHandler.py tests/unit/test_langchain.py
  • PYTHONPYCACHEPREFIX=/private/tmp/langfuse-python-pycache python3 -m py_compile langfuse/langchain/CallbackHandler.py tests/unit/test_langchain.py
  • git diff --check

Greptile Summary

This PR fixes #1602 by propagating the LangChain root run name as the default trace name in Langfuse when no explicit langfuse_trace_name is provided in metadata.

  • One-line fix in on_chain_start: parsed_trace_attributes.get(\"trace_name\", None)parsed_trace_attributes.get(\"trace_name\", span_name), so the LangChain run name flows through as the trace name unless a user-specified override exists.
  • New unit test patches propagate_attributes and asserts that trace_name equals the name kwarg passed to on_chain_start, covering the primary regression path.

Confidence Score: 4/5

The fix is minimal and correct — it threads the LangChain run name through to Langfuse when no explicit override is provided, and does not touch any other code paths.

The core change is a one-line default swap that works as described. The only wrinkle is that get_langchain_run_name can return "<unknown>" as a sentinel, and that sentinel will now be written into the OTel propagation context as an explicit trace name instead of being silently omitted. This is a narrow edge case (unnamed chains with no serialized id), but the behaviour change is observable in Langfuse traces.

langfuse/langchain/CallbackHandler.py — specifically the "<unknown>" sentinel path in get_langchain_run_name

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["on_chain_start called\n(parent_run_id is None)"] --> B["get_langchain_run_name()\n→ span_name"]
    B --> C["_parse_langfuse_trace_attributes(metadata, tags)\n→ parsed_trace_attributes"]
    C --> D{langfuse_trace_name\nin metadata?}
    D -- "Yes" --> E["trace_name = parsed_trace_attributes['trace_name']\n(explicit override)"]
    D -- "No (before PR)" --> F["trace_name = None\n(no name propagated)"]
    D -- "No (after PR)" --> G["trace_name = span_name\n(LangChain run name)"]
    G --> H{span_name resolved?}
    H -- "kwargs['name'] set" --> I["trace_name = kwargs['name']"]
    H -- "serialized['name'] set" --> J["trace_name = serialized['name']"]
    H -- "serialized['id'] set" --> K["trace_name = serialized['id'][-1]"]
    H -- "nothing set" --> L["trace_name = 'unknown'"]
    E --> M["propagate_attributes(trace_name=...)"]
    F --> N["propagate_attributes(trace_name=None)\n→ filtered out, no name set"]
    I --> M
    J --> M
    K --> M
    L --> M
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["on_chain_start called\n(parent_run_id is None)"] --> B["get_langchain_run_name()\n→ span_name"]
    B --> C["_parse_langfuse_trace_attributes(metadata, tags)\n→ parsed_trace_attributes"]
    C --> D{langfuse_trace_name\nin metadata?}
    D -- "Yes" --> E["trace_name = parsed_trace_attributes['trace_name']\n(explicit override)"]
    D -- "No (before PR)" --> F["trace_name = None\n(no name propagated)"]
    D -- "No (after PR)" --> G["trace_name = span_name\n(LangChain run name)"]
    G --> H{span_name resolved?}
    H -- "kwargs['name'] set" --> I["trace_name = kwargs['name']"]
    H -- "serialized['name'] set" --> J["trace_name = serialized['name']"]
    H -- "serialized['id'] set" --> K["trace_name = serialized['id'][-1]"]
    H -- "nothing set" --> L["trace_name = 'unknown'"]
    E --> M["propagate_attributes(trace_name=...)"]
    F --> N["propagate_attributes(trace_name=None)\n→ filtered out, no name set"]
    I --> M
    J --> M
    K --> M
    L --> M
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
langfuse/langchain/CallbackHandler.py:597
When `serialized` carries no name information and the `name` kwarg is absent, `get_langchain_run_name` returns the sentinel string `"<unknown>"`. With this change, `"<unknown>"` will now be propagated as the explicit trace name (it is non-`None`, so `propagate_attributes` won't filter it out and will write it into the OTel context). Before this PR the `None` default caused the `trace_name` attribute to be omitted entirely. Consider guarding against the sentinel so the pre-existing "no-name" behaviour is preserved for that edge case.

```suggestion
                    trace_name=parsed_trace_attributes.get(
                        "trace_name",
                        span_name if span_name != "<unknown>" else None,
                    ),
```

Reviews (1): Last reviewed commit: "fix langchain trace name propagation" | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

@claude claude Bot left a comment

Copy link
Copy Markdown

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.

tags=parsed_trace_attributes.get("tags", None),
metadata=parsed_trace_attributes.get("metadata", None),
trace_name=parsed_trace_attributes.get("trace_name", None),
trace_name=parsed_trace_attributes.get("trace_name", span_name),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 When serialized carries no name information and the name kwarg is absent, get_langchain_run_name returns the sentinel string "<unknown>". With this change, "<unknown>" will now be propagated as the explicit trace name (it is non-None, so propagate_attributes won't filter it out and will write it into the OTel context). Before this PR the None default caused the trace_name attribute to be omitted entirely. Consider guarding against the sentinel so the pre-existing "no-name" behaviour is preserved for that edge case.

Suggested change
trace_name=parsed_trace_attributes.get("trace_name", span_name),
trace_name=parsed_trace_attributes.get(
"trace_name",
span_name if span_name != "<unknown>" else None,
),
Prompt To Fix With AI
This is a comment left during a code review.
Path: langfuse/langchain/CallbackHandler.py
Line: 597

Comment:
When `serialized` carries no name information and the `name` kwarg is absent, `get_langchain_run_name` returns the sentinel string `"<unknown>"`. With this change, `"<unknown>"` will now be propagated as the explicit trace name (it is non-`None`, so `propagate_attributes` won't filter it out and will write it into the OTel context). Before this PR the `None` default caused the `trace_name` attribute to be omitted entirely. Consider guarding against the sentinel so the pre-existing "no-name" behaviour is preserved for that edge case.

```suggestion
                    trace_name=parsed_trace_attributes.get(
                        "trace_name",
                        span_name if span_name != "<unknown>" else None,
                    ),
```

How can I resolve this? If you propose a fix, please make it concise.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


zhangsixiao seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

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