{
  "markdown": "# Langfuse Observability Plugin for Claude Code\n\nThis plugin sends [Claude Code](https://claude.com/claude-code) sessions to\n[Langfuse](https://langfuse.com). It records the user prompts, the agent turns,\nthe model generations with their tokens and cost, and the tool calls, with no\nchange to your code.\n\nLangfuse also documents this integration on the\n[Claude Code integration page](https://langfuse.com/integrations/developer-tools/claude-code).\n\n## What can this integration trace?\n\nThe plugin runs as a Claude Code hook and reads the session transcript on every\nturn, so tracing needs no change to the way you work:\n\n- **Agent turns**: one trace per user prompt, with all turns of a session grouped\n  under one session ID.\n- **Model generations**: every assistant message with inputs, outputs, cost and\n  token usage, including cache reads and a cache write split by the lifetime it\n  was written under, which each carry their own rate.\n  token usage, including cache-read and reasoning splits.\n- **Thinking**: assistant thinking blocks attach to the generation that produced\n  them and render as thinking blocks in the trace. Claude Code writes the\n  thinking text only when `showThinkingSummaries` is `true` in its settings.\n- **Tool calls**: each tool Claude Code invokes, with input and output.\n- **Subagents**: subagent and Workflow-spawned agent transcripts nest under the\n  turn that started them.\n- **Skills**: traces carry a `skill:<name>` tag for every skill a turn invokes.\n- **Images**: pasted images and screenshots from tool results upload as Langfuse\n  media and render inside the trace.\n\nTracing covers the `claude` CLI and desktop **Code** mode; regular Claude Desktop\n**Chat** mode runs no Claude Code hooks and is not traced. \n\n## Prerequisites\n\nOne of:\n\n- [uv](https://docs.astral.sh/uv/) (recommended) on `PATH`. The hook uses\n  `uv run --script` and installs the Langfuse SDK from the script metadata.\n- Python 3.10+ as `python3` with `langfuse>=4.7,<5` installed. This is only a\n  fallback for when `uv` is not on `PATH`.\n\nOn the first hook run, uv downloads the SDK from PyPI and caches it. An offline\nor proxied machine retries that download every turn until the cache is warm, so\npre-warm it from a networked terminal:\n\n```bash\necho '{}' | uv run --quiet --script <plugin-root>/hooks/langfuse_hook.py\n```\n\nWithout a usable runtime the hook exits without tracing, never blocks or slows\nClaude Code, and logs the reason, see [Troubleshooting](#troubleshooting).\n\n## Install\n\n```bash\nclaude plugin marketplace add langfuse/Claude-Observability-Plugin\nclaude plugin install langfuse-observability@langfuse-observability\n```\n\nThe marketplace command registers the plugin marketplace and refreshes its local\ncache. The install command enables the plugin for your Claude Code user scope.\nRestart Claude Code afterwards so the hook configuration is loaded.\n\n## Add your Langfuse credentials\n\nConfigure the plugin from inside a Claude Code session. This is a Claude Code\nslash command, not a shell command:\n\n```text\n$ claude\n> /plugin configure langfuse-observability@langfuse-observability\n```\n\nAlternatively, pass the values during install:\n\n```bash\nclaude plugin install langfuse-observability@langfuse-observability \\\n  --config LANGFUSE_PUBLIC_KEY=pk-lf-... \\\n  --config LANGFUSE_SECRET_KEY=sk-lf-... \\\n  --config LANGFUSE_BASE_URL=https://cloud.langfuse.com\n```\n\nOnly `LANGFUSE_PUBLIC_KEY` and `LANGFUSE_SECRET_KEY` are required; without\n`LANGFUSE_BASE_URL` the plugin uses `https://cloud.langfuse.com` (EU region).\nGet keys from your Langfuse project settings → API Keys. The secret key is held\nin your OS keychain, not in a file.\n\n## Configuration options\n\nSet these through `/plugin configure` or `--config`, unless the table says the\nvalue is a per-run environment variable.\n\n| Option | Description | Required |\n| ------ | ----------- | -------- |\n| `LANGFUSE_SECRET_KEY` | Your Langfuse secret key (`sk-lf-...`). Held in your OS keychain. | Yes |\n| `LANGFUSE_PUBLIC_KEY` | Your Langfuse public key (`pk-lf-...`). | Yes |\n| `LANGFUSE_BASE_URL` | Langfuse host. EU: `https://cloud.langfuse.com`, US: `https://us.cloud.langfuse.com`, or your self-hosted URL. | No (defaults to EU) |\n| `LANGFUSE_USER_ID` | User identifier attached to every trace, shown as the user in Langfuse. | No |\n| `CC_LANGFUSE_DEBUG` | Verbose logging to the hook log (default `false`). | No |\n| `CC_LANGFUSE_MAX_CHARS` | Truncate captured inputs and outputs to this many characters (default `20000`). | No |\n| `CC_LANGFUSE_SKILL_TAGS` | Tag traces with `skill:<name>` for every skill invoked in the turn (default `true`). | No |\n| `CC_LANGFUSE_CAPTURE_SKILL_CONTENT` | Include injected skill instruction text in the Skill tool span output (default `false`). | No |\n| `CC_LANGFUSE_CAPTURE_IMAGES` | Upload images to Langfuse and show them in the trace (default `true`). Needs media upload on your deployment (self-hosted: `LANGFUSE_S3_MEDIA_UPLOAD_*`). Set it to `false` if media upload is unavailable: the trace then shows a marker per image, such as `[image image/png ~200KB]`. | No |\n| `CC_LANGFUSE_STATE_DIR` | Absolute directory (`~` is expanded) for the hook's state, lock and log files (default `~/.claude/state`). Set one per `CLAUDE_CONFIG_DIR` installation to keep them apart. An unusable value falls back to the default and logs a warning. | No |\n| `CC_LANGFUSE_TRACE_SEED` | Seed that makes trace IDs predictable, so a headless caller can derive a run's trace ID before the trace exists. Use a unique seed per session, otherwise sessions collide on the same trace IDs. | No |\n| `CC_LANGFUSE_TRACEPARENT` | Per-run environment variable. W3C traceparent of an existing trace to attach to — see [Attach runs to an existing trace](#attach-runs-to-an-existing-trace). | No |\n| `CC_LANGFUSE_PARENT_TRACE_ID` / `CC_LANGFUSE_PARENT_SPAN_ID` | Per-run environment variables. Explicit alternative to `CC_LANGFUSE_TRACEPARENT` (32-hex trace id plus 16-hex span id). | No |\n\n## Update\n\nUpdates are manual since auto-update is off by default for marketplaces outside Anthropic's.\n\n```bash\nclaude plugin marketplace update langfuse-observability\nclaude plugin update langfuse-observability@langfuse-observability\n```\n\nThe marketplace command pulls the current code from GitHub into the local\nmarketplace clone. Restart Claude Code\nafterwards so the new hook is loaded.\n\nTo see which version is installed:\n\n```bash\nclaude plugin list\n```\n\n## Enable and disable tracing\n\n| Scope | How |\n| ----- | --- |\n| Change the settings | `/plugin configure langfuse-observability@langfuse-observability` in a session |\n| Stop tracing new sessions | `claude plugin disable langfuse-observability@langfuse-observability --scope user` |\n| Start tracing again | `claude plugin enable langfuse-observability@langfuse-observability --scope user` |\n| Remove the plugin | `claude plugin uninstall langfuse-observability` |\n\nDisabling keeps the plugin installed and keeps your configuration. Check the\nresult with `claude plugin list`.\n\nA session that already runs loads its hooks at startup, so restart it after you\ndisable the plugin. Enable and disable can be project-scoped, so run\n`claude plugin list` from the directory you work in.\n\n## Attach runs to an existing trace\n\nWhen your application launches Claude Code headlessly (`claude -p`) as one step\nof an already-instrumented workflow, the run can join your existing Langfuse\ntrace instead of creating its own root trace. Create a span for the agent run,\nthen pass its trace context when you launch Claude Code:\n\n```python\nwith langfuse.start_as_current_span(name=\"Claude Code run\") as run_span:\n    traceparent = f\"00-{run_span.trace_id}-{run_span.id}-01\"\n    subprocess.run(\n        [\"claude\", \"-p\", \"Refactor utils.py\"],\n        env={**os.environ, \"CC_LANGFUSE_TRACEPARENT\": traceparent},\n    )\n```\n\nEvery turn of the session, with its model calls, tool calls and subagents, then\nappears under your `Claude Code run` span. `CC_LANGFUSE_PARENT_TRACE_ID` and\n`CC_LANGFUSE_PARENT_SPAN_ID` are an explicit alternative to the traceparent.\n\n## Troubleshooting\n\nNearly every failure explains itself in `~/.claude/state/langfuse_hook.log`, or\nin `CC_LANGFUSE_STATE_DIR` if you set a usable one. Send one message, then match\nthe newest lines against this table:\n\n| What the log shows | What to do |\n| ------------------ | ---------- |\n| No new lines at all | The hook never launched: the plugin is disabled, no usable runtime was found, or uv could not download the SDK. Run `claude plugin list` from the directory you use, and put uv on the `PATH` of the app that launches Claude Code. |\n| `langfuse import failed (…) python=… PATH=…` | The Python that ran the hook cannot import the SDK. The line names the interpreter and PATH. Install uv on that PATH, or make `python3` a 3.10+ environment with `langfuse>=4.7,<5`. |\n| `Langfuse config incomplete: missing …` | The named keys did not reach the hook. Configure them with `/plugin configure`. If the line also says `loaded under plugin identity '@inline'`, see below. |\n| `Hook started` plus a skip reason | The hook ran and skipped on purpose, which is usual for background sessions. Report it with the log line if real turns are missing. |\n| `Processed N turns …` but nothing in Langfuse | Delivery failed after the SDK took the turns. Check `LANGFUSE_BASE_URL` (EU against US), key validity, and proxy reachability. |\n\n`Hook started` and other `[DEBUG]` lines need `CC_LANGFUSE_DEBUG`. The failure\nlines above are `[INFO]` and appear without it.\n\n### Desktop app (GUI) sessions\n\nA GUI app does not read your shell profile and resolves `PATH` once at launch, so\na variable you `export` in `~/.zshrc` never reaches a GUI-spawned hook. After you\ninstall uv, fully quit and relaunch the app, and set keys through\n`/plugin configure` rather than shell exports.\n\nRecent Claude Desktop builds also load user-installed plugins under a second\nplugin identity (`langfuse-observability@inline`), so keys from\n`/plugin configure` never reach the hook: terminal sessions work while desktop\nsessions stay silent. The log line then contains\n`loaded under plugin identity '@inline'`. As a temporary workaround, repeat your\noptions under that identity in `~/.claude/settings.json`:\n\n```json\n\"pluginConfigs\": {\n  \"langfuse-observability@inline\": {\n    \"options\": {\n      \"LANGFUSE_PUBLIC_KEY\": \"pk-lf-...\",\n      \"LANGFUSE_SECRET_KEY\": \"sk-lf-...\",\n      \"LANGFUSE_BASE_URL\": \"https://cloud.langfuse.com\"\n    }\n  }\n}\n```\n\nThe entry needs `LANGFUSE_SECRET_KEY`, because the keychain secret applies only\nto the installed identity. It is plain text, so use a dedicated key pair and\nremove the entry once it is no longer needed. Settings are read on the next\nmessage, with no restart.\n\n## Development\n\n```bash\nuv run --group dev pytest\n```\n\nThe hook is a single uv script with inline dependency metadata, and Claude Code\nruns `hooks/langfuse_hook.py` directly, so there is no build step.\n\n## License\n\n[MIT](./LICENSE)\n",
  "bytes": 10981,
  "sha": "061cc5ffe063a063ea17dc1b94ea8d79551c077502ee5c774ebc951e46c017d3",
  "repo_slug": "langfuse/claude-observability-plugin",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_langfuse_claude_observability_plugin_lan_bbf1c7dc/readme"
}