feat(hook): operator trace tags via CC_LANGFUSE_TAGS - #61
Open
atoomic wants to merge 3 commits into
Open
Conversation
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>
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What the PR says (how / what / why)