Claude Code plugin that sends Claude Code session telemetry to Langfuse. It traces user prompts, agent turns, model generations with their token usage and cost, thinking blocks, tool calls, skills, subagents, and pasted or returned images.
Langfuse also documents this integration on the Claude Code integration page.
Add the marketplace and install the plugin:
claude plugin marketplace add langfuse/Claude-Observability-Plugin
claude plugin install langfuse-observability@langfuse-observabilityRestart Claude Code after installing, so that it loads the hook.
Tracing covers the claude CLI and the desktop app in Code mode. Claude
Desktop Chat mode runs no Claude Code hooks and is not traced.
The plugin needs uv on PATH. The hook runs as a
uv script and installs the Langfuse SDK from its own inline script metadata.
Machines without uv fall back to Python 3.10+ as python3 with
langfuse>=4.7,<5 installed.
Without a usable runtime the hook writes the reason to its log and exits. It never blocks or slows Claude Code.
Configure the plugin from inside a Claude Code session. This is a Claude Code slash command, not a shell command:
/plugin configure langfuse-observability@langfuse-observability
You can also pass the values during install:
claude plugin install langfuse-observability@langfuse-observability \
--config LANGFUSE_PUBLIC_KEY=pk-lf-... \
--config LANGFUSE_SECRET_KEY=sk-lf-... \
--config LANGFUSE_BASE_URL=https://cloud.langfuse.comOnly LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY are required. Without
LANGFUSE_BASE_URL the plugin uses https://cloud.langfuse.com (EU region).
Get the keys from the API Keys page in your Langfuse project settings. The
secret key is held in your OS keychain, not in a file.
/plugin configure also lists the optional settings, such as the user ID,
debug logging, and image capture, with their defaults.
Automatic updates are off for marketplaces outside Anthropic's, so you run the update yourself.
claude plugin marketplace update langfuse-observability
claude plugin update langfuse-observability@langfuse-observabilityRestart Claude Code afterwards. claude plugin list shows the installed
version.
A session that was already running keeps the hook version it started with,
including a resumed one: the update reaches it only after a restart. Since
1.2.2 every firing writes langfuse_agents.<key>.json beside the state file
(~/.claude/state by default), so a state directory with no such file has not
run 1.2.2 or later yet.
A launcher can start an agent's prompt with one line that says what the work is for:
[tc migration=m1 batch=b2 fqn=pkg.a:svc phase=codegen]
The hook reads it from the agent's full prompt, not from the span input,
which is truncated to CC_LANGFUSE_MAX_CHARS. It counts on the first non-blank
line, or on any line that begins with it in the first 4000 characters. A
workflow agent's computed task is read before the relayed user request above
it.
- Each pair becomes
tc_<key>in the agent span's metadata. Empty values (pr=) are dropped. tc_sourcesays where they came from:own,inherited(from the agent or main-thread turn that launched it, at any depth, workflow agents included), ornone. An own header replaces the inherited one whole.- A main-thread turn whose prompt has a header carries it on its root span.
The root also gets
tc_session_agentswhen it ends: the session's agents so far by source, withunattributedfor those with no header to own or inherit. migrationandworkalso become trace tags (tc:migration=m1).
Set CC_LANGFUSE_TC_HEADERS=false to turn this off.
The hook can re-upload a finished session from its transcript, for example to repair traces a version with a known gap exported incompletely. Run it only after the session has ended.
uv run --script hooks/langfuse_hook.py \
--backfill ~/.claude/projects/<project>/<session-id>.jsonl --dry-run--dry-run builds every observation, sends nothing, and prints the counts
and token totals that would be exported. Without it, the same Langfuse keys as
the hook are read from the environment.
- The re-uploaded turns go to traces of their own (
--namespace, defaultbackfill-v1). Langfuse v4 does not merge two uploads of one observation id, so a namespace that already holds traces of the session is refused; supersede an earlier backfill with a new namespace. - After the upload, the backfill reads the session back from Langfuse until
every observation it sent is visible (up to 10 minutes). If any is
missing it exits with code 4 and deletes nothing: a rejected upload is
only logged by the exporter, never raised.
--verify-minuteschanges the window. - After exit 4: if Langfuse is only slow to ingest, run again with
--verify-onlyand the same--namespace. It uploads nothing (the ids are deterministic), re-reads, and with--replace-session-tracesdeletes the old traces once everything is visible. If the upload really failed, start over with a new--namespaceand--replace-session-traces; that also removes the partial upload. --replace-session-tracesthen deletes the session's traces this hook namedClaude Code Turnthat are not the backfill's own. Traces under another name are never touched. With--dry-runit lists them instead.- The session id must be the transcript's own (file name and rows), and a
session with any file under its directory (agents, workflows) modified in
the last 30 minutes is refused unless
--forceis passed.
The hook explains nearly every failure in
~/.claude/state/langfuse_hook.log. Send one message, then read the newest
lines. Turn on CC_LANGFUSE_DEBUG for verbose logging.
A log with no new lines means that the hook never started. Either the plugin is
disabled, or no usable runtime was found. Run claude plugin list from the
directory you work in, and put uv on the PATH of the app that starts Claude
Code.
See the contributing guide.