Description
collect_skill_tags() in hooks/langfuse_hook.py is supposed to add a skill:<name> tag to every trace where a skill ran during the turn, gated by CC_LANGFUSE_SKILL_TAGS (default true). It only detects skills invoked via a tool_use block literally named "Skill".
Claude Code has a second invocation path that never produces that block: slash commands mapped to a skill (e.g. /some-skill). These instead appear as a <command-name>/<command-message> block on the user row, followed by assistant rows carrying a top-level attributionSkill field (sibling of type/uuid/message) set to the skill's slug.
langfuse_hook.py never reads attributionSkill, and trace_metadata in emit_turn() is built from a fixed field allowlist, so the value can't pass through incidentally. Result: any skill invoked via slash command is invisible to skill-based tagging/filtering in Langfuse, regardless of which skill it is.
Steps to Reproduce
- Configure the plugin with valid Langfuse credentials; leave
CC_LANGFUSE_SKILL_TAGS at its default.
- Invoke any skill via its slash-command form (e.g.
/<skill-name>) and let the turn complete.
- In the session transcript, confirm no
tool_use block named "Skill" exists for the invocation, but a following assistant row has top-level "attributionSkill": "<skill-name>".
- Query Langfuse:
langfuse api traces list --tags "skill:<skill-name>".
Expected Behavior
The trace(s) for that turn carry a skill:<skill-name> tag, and the query in step 4 returns them.
Actual Behavior
The trace ships with only the generic claude-code tag; the tag-filtered query returns zero results. The tag is missing, not the trace itself — metadata and content are otherwise complete.
Environment
- Plugin:
langfuse-observability v1.0.0 (github.com/langfuse/Claude-Observability-Plugin)
- Langfuse SDK:
langfuse>=4.0,<5
- OS: macOS
Description
collect_skill_tags()inhooks/langfuse_hook.pyis supposed to add askill:<name>tag to every trace where a skill ran during the turn, gated byCC_LANGFUSE_SKILL_TAGS(defaulttrue). It only detects skills invoked via atool_useblock literally named"Skill".Claude Code has a second invocation path that never produces that block: slash commands mapped to a skill (e.g.
/some-skill). These instead appear as a<command-name>/<command-message>block on the user row, followed by assistant rows carrying a top-levelattributionSkillfield (sibling oftype/uuid/message) set to the skill's slug.langfuse_hook.pynever readsattributionSkill, andtrace_metadatainemit_turn()is built from a fixed field allowlist, so the value can't pass through incidentally. Result: any skill invoked via slash command is invisible to skill-based tagging/filtering in Langfuse, regardless of which skill it is.Steps to Reproduce
CC_LANGFUSE_SKILL_TAGSat its default./<skill-name>) and let the turn complete.tool_useblock named"Skill"exists for the invocation, but a following assistant row has top-level"attributionSkill": "<skill-name>".langfuse api traces list --tags "skill:<skill-name>".Expected Behavior
The trace(s) for that turn carry a
skill:<skill-name>tag, and the query in step 4 returns them.Actual Behavior
The trace ships with only the generic
claude-codetag; the tag-filtered query returns zero results. The tag is missing, not the trace itself — metadata and content are otherwise complete.Environment
langfuse-observabilityv1.0.0 (github.com/langfuse/Claude-Observability-Plugin)langfuse>=4.0,<5