Skip to content

fix(tracing): keep propagated tags intact across baggage headers - #1919

Open
lets-order-some-fries wants to merge 1 commit into
langfuse:mainfrom
lets-order-some-fries:fix/baggage-tags-separator
Open

lets-order-some-fries wants to merge 1 commit into
langfuse:mainfrom
lets-order-some-fries:fix/baggage-tags-separator

Conversation

@lets-order-some-fries

@lets-order-some-fries lets-order-some-fries commented Oct 1, 2026 •

Copy link
Copy Markdown

What does this PR do?

No linked issue. I found this while reading propagation.py.

propagate_attributes(tags=[...], as_baggage=True) puts the tag list itself into baggage (langfuse/_client/propagation.py:606 at 0bc5897), and OTel's W3CBaggagePropagator writes every value with str(value). The receiving service passes the decoded string straight through (propagation.py:495), so its spans get langfuse.trace.tags set to the string "['tag-a', 'tag-b']" instead of a list. Only tags are affected.

The wire format stays, because Langfuse's AI gateway already parses this form (parse_tags in ai-gateway/src/telemetry/context.rs, from langfuse/langfuse#17549). So the fix is read-side only: a langfuse_tags value starting with [ is parsed as a JSON array, then as a Python list literal, and kept only if it is a list of strings. Anything else is split on ,, as the JS SDK writes it. ast.literal_eval runs no code, OTel drops baggage entries over 4096 bytes, and malformed or deeply nested input raises and falls through to the split.

Tags with commas now survive a Python-to-Python hop. A JS service still splits Python-written tags on commas; that fix belongs in langfuse-js.

The existing baggage tests stay in one process, where the in-context list hides the baggage value. The new tests extract a real header into an empty context; on main the first fails with assert "['tag-a', 'comma,tag']" == ('tag-a', 'comma,tag').

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Refactor
  • Documentation update
  • Tooling, CI, or repo maintenance

Verification

List the main commands you ran:

uv sync --locked
uv run --frozen ruff check .                        # All checks passed!
uv run --frozen ruff format --check .               # flags only tests/unit/test_media.py, same on main
uv run --frozen mypy langfuse --no-error-summary    # exit 0
uv run --frozen pytest tests/unit/test_propagate_attributes.py   # 136 passed on 3.11.8, 3.10.21, 3.14.6
uv run --frozen pytest -n auto --dist worksteal tests/unit       # 712 passed, 2 skipped

The gateway's tests/fixtures/python-baggage.json carrier now reads back as its ten tags, and the SDK writes a byte-identical langfuse_tags entry for them. I did not run e2e or live_provider, since unit tests cover the change without a server.

Checklist

  • I self-reviewed the diff using code_review.md.
  • I added or updated tests for behavior changes.
  • I updated docs, examples, or .env.template if needed. (not needed)
  • I did not hand-edit generated files; if generated files changed, I used the upstream regeneration path.
  • I did not commit secrets or credentials.

🤖 Generated with Claude Code

RetriggerConfidence Score: 4/5

The parsing change has a narrow tag-corruption edge case, and the repository’s import-placement requirement must be satisfied before merging.

Summary

The PR reads cross-service baggage tags as lists while leaving the Python baggage wire format unchanged.

  • Parses JSON arrays and Python list representations, with a comma-delimited fallback.
  • Adds header-extraction tests for Python, JS-style, and JSON values.

Reviews (1) · Last reviewed commit: "fix(tracing): keep propagated tags intac..."

propagate_attributes(tags=..., as_baggage=True) sends tags as the Python
list literal "['tag-a', 'tag-b']", because the W3C baggage propagator
writes str(value). The receiving service set that string as
langfuse.trace.tags instead of a list.

Parse tags when reading them from baggage: a JSON array or a Python list
literal, falling back to a comma split for the JS SDK's "tag-a,tag-b".
The wire format is unchanged, since Langfuse's AI gateway already parses
it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@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.

@CLAassistant

CLAassistant commented Oct 1, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Comment on lines +563 to +564
if isinstance(tags, list) and all(isinstance(tag, str) for tag in tags):
return tags

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 List-shaped tags change meaning A JS-originated single tag whose literal value is ["red","blue"] is parsed as two tags here. The downstream span records red and blue instead of the original tag, making trace tags unreliable for this valid value.

Knowledge Base Used: OpenTelemetry propagation and export

Prompt To Fix With AI
This is a comment left during a code review.
Path: langfuse/_client/propagation.py
Line: 563-564

Comment:
**List-shaped tags change meaning** A JS-originated single tag whose literal value is `["red","blue"]` is parsed as two tags here. The downstream span records `red` and `blue` instead of the original tag, making trace tags unreliable for this valid value.

**Knowledge Base Used:** [OpenTelemetry propagation and export](https://app.greptile.com/personal-org-4986/-/custom-context/knowledge-base/langfuse/langfuse-python/-/docs/opentelemetry-propagation-and-export.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch on the edge case. A single tag whose text is itself a JSON array is ambiguous on the wire: the AI gateway's parse_tags reads ["red","blue"] as two tags as well, and the JS SDK's comma split already breaks that tag apart today. I kept the reader consistent with the gateway, but I'm happy to narrow it if you'd prefer.

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.

That’s a fair clarification. Given that the wire format is already ambiguous and the gateway’s parse_tags applies the same interpretation, keeping the reader aligned with the gateway is the least surprising behavior. The JS SDK’s comma-delimited format also cannot preserve a tag containing commas, so narrowing only the Python reader would not restore round-trip fidelity. I’m withdrawing this edge-case finding; no change is needed here.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

This branch has not been deployed

No deployments
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.

2 participants