Skip to content

feat(hook): operator trace tags via CC_LANGFUSE_TAGS - #61

Open
atoomic wants to merge 3 commits into
langfuse:mainfrom
atoomic:feat/custom-trace-tags
Open

feat(hook): operator trace tags via CC_LANGFUSE_TAGS#61
atoomic wants to merge 3 commits into
langfuse:mainfrom
atoomic:feat/custom-trace-tags

Conversation

@atoomic

@atoomic atoomic commented Aug 18, 2026

Copy link
Copy Markdown

What the PR says (how / what / why)

  • What — CC_LANGFUSE_TAGS=env:prod,team:platform, read through the existing _opt(), appended between claude-code and the skill tags.
  • Why — an orchestrator spawning claude -p can't label its traces: get_trace_tags() is a fixed list, TRACE_NAME is a constant, metadata has no env passthrough, no extension point. And the argument that should carry the most weight with them: their own sibling codex-observability-plugin already ships LANGFUSE_CODEX_TAGS — the Claude plugin is the odd one out.
  • How — three cherry-pickable commits, so they can take commit 1 alone if they prefer Add support for custom tags via CC_LANGFUSE_TAGS #37's minimalism. Plus the two points a reviewer would otherwise challenge: why the parser returns (tags, warning) instead of logging (import-time, and _get_logger() swallows exceptions, so an inline log would fail silently), and why the tags sit outside the SKILL_TAGS gate with a deduped return.
  • Links — credits Add support for custom tags via CC_LANGFUSE_TAGS #37 and offers to close in its favour, notes feat(hook): custom trace tags and session grouping via commands #27 composes rather than competes (and the test_custom_tags.py filename collision), and gives feat: attach project name to trace metadata #60 the tags-vs-metadata filterability datum.
  • Divergence stated plainly — no plugin.json entry, with the reasoning and an explicit offer to add it.

atoomic and others added 3 commits August 17, 2026 17:43
An orchestrator that spawns `claude -p` — a CI harness, a benchmark
runner, a bot framework — has no way to label the traces it produces.
get_trace_tags() returns a fixed ["claude-code"] plus skill tags,
TRACE_NAME is a constant, trace metadata has no env passthrough, and the
hook exposes no extension point. LANGFUSE_USER_ID is the only
env-settable identity field and is usually already spent on identity.

Read CC_LANGFUSE_TAGS through the existing _opt() resolver as a
comma-separated list and append it to every trace's tags, between
"claude-code" and the skill tags.

- Tokens are stripped, empties dropped, and repeats collapse onto their
  first occurrence, so "a, b,,a," yields ["a", "b"].
- Custom tags sit outside the SKILL_TAGS gate: turning skill tags off
  should not discard labels the launching process explicitly asked for.
- The result is de-duplicated, so a custom tag repeating "claude-code"
  or a skill tag appears once, keeping its custom position. This is a
  no-op for every pre-existing input.
- No effect in attached mode, where emit_turn skips propagate_attributes
  because the launching application owns the trace's tags. The comment
  there now says so.

The sibling codex-observability-plugin already ships the equivalent
LANGFUSE_CODEX_TAGS, so the Claude plugin is the odd one out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Caps the operator tag list at 20 tags of 200 characters each. 200 is the
Langfuse SDK's own per-tag limit, so a tag this hook accepts is never
dropped again downstream — and one it rejects is now reported in
~/.claude/state/langfuse_hook.log, where users already look, instead of
vanishing into the SDK's logger. Over-long tags are dropped rather than
truncated: a truncated tag is a *wrong* tag, and Langfuse tag facets are
project-global and awkward to clean up afterwards.

The warning is returned from parse_custom_tags() as the second element of
a (tags, warning) pair rather than logged in place. CUSTOM_TAGS is a
module-level constant evaluated at import time, where info() and
_get_logger() do not yet exist — and because _get_logger() swallows
exceptions, an inline log call would not crash, it would *silently* do
nothing. _resolve_state_dir() already solved this exact problem the same
way, so main() now emits both warnings side by side.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
New tests/unit/test_custom_tags.py exercises the parser directly:
empty/whitespace/comma-only input, stripping, order-preserving dedup,
interior spaces surviving as one tag, the count cap, an over-long tag
dropped rather than truncated, the exactly-at-the-limit boundary, and a
100 KB pathological value returning without raising.

tests/unit/test_skill_tags.py covers composition: custom tags follow
"claude-code" and precede skill tags; absence leaves the tag list
untouched; they still apply with CC_LANGFUSE_SKILL_TAGS=false; and both
dedup collisions (a custom tag repeating "claude-code" or a skill tag)
collapse to one entry.

tests/unit/test_config_precedence.py pins the resolver choice: env beats
the wizard option, an empty env value falls through to it, and a bare
LANGFUSE_TAGS is deliberately NOT read — this option resolves through
_opt, not _core_opt, so it does not claim a name the Langfuse SDK
doesn't define.

Note the harness constraint both files work around: the hook_module
fixture is session-scoped, so CUSTOM_TAGS freezes at import and
monkeypatch.setenv has no effect. Composition tests set the parsed value
with monkeypatch.setattr, and test_skill_tags gains an autouse fixture
resetting it so a developer with CC_LANGFUSE_TAGS exported in their own
shell doesn't see phantom failures.

Co-Authored-By: Claude Opus 5 (1M context) <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 Aug 18, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@atoomic

atoomic commented Aug 18, 2026

Copy link
Copy Markdown
Author

Raised the underlying need as #62 as well, so the design questions here (particularly the import-time logging one) can be settled independently of whether this particular patch is the one that lands.

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