Capture every Claude Code conversation — prompts, responses, tool calls, and session grouping — using a self-hosted instance or Langfuse Cloud.
This template provides a complete, production-ready setup for observing your Claude Code sessions using Langfuse. Choose between self-hosted (everything runs locally in Docker) or Langfuse Cloud (zero infrastructure), with automatic session tracking and incremental state management.
Read the full story: I Built My Own Observability for Claude Code — why I built this, how it works, and screenshots of the setup in action.
Want more AI implementation patterns like this? Subscribe to Signal over Noise — weekly frameworks for building production AI systems, from someone who's done it at Deutsche Bank, Snowflake, and ClickHouse.
| Self-Hosted | Langfuse Cloud | |
|---|---|---|
| Infrastructure | Docker on your machine | None (fully managed) |
| Prerequisites | Python 3.11+, Docker, 4-6GB RAM, 2-5GB disk | Python 3.11+ |
| Setup time | ~5 minutes | ~2 minutes |
| Data location | Your machine only | Langfuse Cloud (EU or US) |
| Cost | Free (self-hosted) | Free tier available |
Use this to run everything locally in Docker. No external accounts needed.
Prerequisites: Docker and Docker Compose, Python 3.11+, Claude Code CLI, 4-6GB RAM, 2-5GB disk.
-
Clone the repository
git clone https://github.com/doneyli/claude-code-langfuse-template.git cd claude-code-langfuse-templateOptional: Verify prerequisites
./scripts/validate-setup.sh
-
Generate credentials
cp .env.example .env ./scripts/generate-env.sh
This will generate secure random credentials and prompt for your email/name.
-
Start Langfuse
docker compose up -d
Wait 30-60 seconds for all services to initialize.
-
Install the hook
./scripts/install-hook.sh
This installs the Python package and configures Claude Code to send traces to Langfuse.
-
Verify the setup
- Open http://localhost:3050 in your browser
- Log in with the credentials from your
.envfile - Start a Claude Code conversation
- Watch traces appear in Langfuse in real-time
Optional: Run full validation
./scripts/validate-setup.sh --post
Use this if you have (or want to create) a Langfuse Cloud account. No Docker required.
Prerequisites: Python 3.11+, Claude Code CLI, a Langfuse Cloud account with API keys.
-
Clone the repository
git clone https://github.com/doneyli/claude-code-langfuse-template.git cd claude-code-langfuse-template -
Install the hook (cloud mode)
./scripts/install-hook.sh --cloud
You'll be prompted for your public key, secret key, and region (EU/US/custom).
-
Verify the setup
./scripts/validate-setup.sh --cloud --post
Then start a Claude Code conversation — traces will appear in your Langfuse Cloud dashboard.
If you already run Langfuse (self-hosted or Cloud), you can skip the Docker setup entirely and just install the hook. This uses uv for isolated, zero-pollution dependency management.
- uv installed
- A running Langfuse instance with a project and API keys
- Claude Code CLI
-
Clone the repository (for the hook script only)
git clone https://github.com/doneyli/claude-code-langfuse-template.git cd claude-code-langfuse-template -
Copy the hook to your Claude config
mkdir -p ~/.claude/hooks cp hooks/langfuse_hook.py ~/.claude/hooks/langfuse_hook.py
-
Configure Claude Code settings
Edit
~/.claude/settings.json(create if it doesn't exist):{ "env": { "TRACE_TO_LANGFUSE": "true", "LANGFUSE_PUBLIC_KEY": "pk-lf-...", "LANGFUSE_SECRET_KEY": "sk-lf-...", "LANGFUSE_HOST": "https://cloud.langfuse.com" }, "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "uv run --with 'langfuse>=3.0,<4.0' --python 3.12 ~/.claude/hooks/langfuse_hook.py" } ] } ] } }See
settings-examples/global-settings-uv.jsonfor a complete example. -
Verify — Start a Claude Code conversation and check your Langfuse dashboard for traces.
uv run --with 'langfuse>=3.0,<4.0' creates an isolated ephemeral virtual environment with the langfuse package, without touching your system Python. The first run downloads the dependency (~1-5s), subsequent runs resolve from cache (~50-150ms overhead).
Always pin the dependency version range. The --with 'langfuse>=3.0,<4.0' flag ensures you stay within a known-good major version. Without pinning, uv resolves the latest version on every run, which could silently pull a compromised release.
Do not use uvx --from git+https://... patterns — running code directly from a git remote without commit pinning introduces supply chain risk.
| Item | Description |
|---|---|
| User prompts | Full text of every user message |
| Assistant responses | Complete assistant replies, including reasoning |
| Tool calls | Name, input parameters, and output for every tool invocation |
| Session grouping | All turns in a Claude Code session grouped together |
| Model info | Model name and version used for each response |
| Timing | Duration of each turn and tool call |
| Project context | Project name extracted from workspace path |
Once you have traces flowing, run the built-in analyzer to find patterns in how Claude Code uses tools, how session length affects productivity, and whether your prompting habits pay off.
Quick start (self-hosted, ClickHouse direct):
./scripts/analyze-traces.shSDK / REST API (works with Cloud too):
pip install langfuse
export LANGFUSE_PUBLIC_KEY=$(docker exec langfuse-web printenv LANGFUSE_INIT_PROJECT_PUBLIC_KEY)
export LANGFUSE_SECRET_KEY=$(docker exec langfuse-web printenv LANGFUSE_INIT_PROJECT_SECRET_KEY)
python3 scripts/analyze-traces-sdk.pyBoth scripts produce five analyses: tool usage distribution, session turn distribution, productivity by session length, and read-before-edit patterns. Add --json for machine-readable output or --tag <project> to filter by project.
See docs/trace-analysis.md for the full methodology, ClickHouse schema reference, and a query cookbook for writing your own analytics.
The Langfuse hook runs as a Claude Code Stop hook — it executes after each assistant response completes.
Architecture:
┌─────────────┐
│ Claude Code │
│ (Desktop) │
└──────┬──────┘
│
│ Writes transcript after each turn
▼
┌─────────────────────┐
│ transcript.jsonl │
│ ~/.claude/projects/ │
└──────┬──────────────┘
│
│ Stop hook triggers
▼
┌──────────────────────┐
│ langfuse_hook.py │
│ - Parses transcript │
│ - Tracks state │
│ - Groups by session │
└──────┬───────────────┘
│
│ HTTP POST
▼
┌──────────────────────────────┐
│ Langfuse API │
│ (localhost:3050 or Cloud) │
└──────┬───────────────────────┘
│
▼
┌──────────────────────────┐
│ PostgreSQL + ClickHouse │
│ (traces, analytics) │
└──────────────────────────┘
Key Features:
- Incremental state tracking: Only processes new messages since last run
- Session grouping: All turns in a conversation are linked by session ID
- Tool call tracking: Each tool invocation is captured as a span with input/output
- Graceful failure: Errors are logged but don't interrupt Claude Code
- Opt-in by default: Only runs when
TRACE_TO_LANGFUSE=true
The installation script sets up global tracing by default (all Claude Code sessions are captured).
To opt out for a specific project:
- Create
.claude/settings.local.jsonin your project root - Add the opt-out configuration:
{ "env": { "TRACE_TO_LANGFUSE": "false" } }
See settings-examples/project-opt-out.json for a complete example.
If you're using Langfuse Cloud and want to see or tweak the generated configuration, see settings-examples/cloud-settings.json.
All configuration is managed through environment variables in ~/.claude/settings.json:
TRACE_TO_LANGFUSE: Enable/disable tracing (trueorfalse)LANGFUSE_PUBLIC_KEY: Project public key (auto-generated)LANGFUSE_SECRET_KEY: Project secret key (auto-generated)LANGFUSE_HOST: Langfuse URL (default:http://localhost:3050)CC_LANGFUSE_DEBUG: Enable debug logging (trueorfalse)
Change the Langfuse port:
Edit docker-compose.yml and update the langfuse-web port mapping:
ports:
- 3051:3000 # Change 3050 to 3051Also update LANGFUSE_HOST in your .env.example and regenerate credentials.
Add custom tags:
Edit hooks/langfuse_hook.py and modify the tags list in the create_trace() function:
tags = ["claude-code", "my-custom-tag"]Adjust log retention: By default, logs are kept indefinitely. To clean up old logs:
# Clear hook logs older than 7 days
find ~/.claude/state -name "langfuse_hook.log" -mtime +7 -deleteView logs:
# Langfuse web logs
docker compose logs -f langfuse-web
# All services
docker compose logs -f
# Hook execution logs
tail -f ~/.claude/state/langfuse_hook.logRestart services:
docker compose restartStop services:
docker compose downStop and remove all data:
docker compose down -vUpdate Langfuse to latest version:
docker compose pull
docker compose up -dCheck service health:
docker compose psAccess the database directly:
# PostgreSQL
docker compose exec postgres psql -U postgres
# ClickHouse
docker compose exec clickhouse clickhouse-clientEnable debug logging:
Edit ~/.claude/settings.json and add:
{
"env": {
"CC_LANGFUSE_DEBUG": "true"
}
}Then check ~/.claude/state/langfuse_hook.log for detailed execution logs.
Verify the hook is running:
# Check if hook is registered
cat ~/.claude/settings.json | grep -A 5 "Stop"
# Check hook logs
tail -20 ~/.claude/state/langfuse_hook.logTest the hook manually:
TRACE_TO_LANGFUSE=true \
LANGFUSE_PUBLIC_KEY=pk-lf-local-claude-code \
LANGFUSE_SECRET_KEY=your-secret-key \
LANGFUSE_HOST=http://localhost:3050 \
python3 ~/.claude/hooks/langfuse_hook.pySymptom: docker compose up -d fails with connection error
Solution:
# macOS
open -a Docker
# Linux
sudo systemctl start dockerSymptom: install-hook.sh reports Python 3.11+ required
Solution:
# macOS (Homebrew)
brew install python@3.12
# Ubuntu/Debian
sudo apt install python3.12
# Or use pyenv
pyenv install 3.12
pyenv global 3.12Symptom: Docker fails to start because port 3050 is in use
Solution:
# Find what's using the port
lsof -i :3050
# Either stop that service, or change the Langfuse port in docker-compose.ymlSymptom: Traces don't appear in Langfuse Cloud, hook logs show 401/403 errors
Check:
- Keys must start with
pk-lf-(public) andsk-lf-(secret) - Verify keys match the correct project in your Langfuse Cloud dashboard
- Ensure
LANGFUSE_HOSTmatches your region (https://cloud.langfuse.comfor EU,https://us.cloud.langfuse.comfor US) - Re-run:
./scripts/install-hook.sh --cloud
Symptom: validate-setup.sh --cloud --post warns that the API is not reachable
Check:
- Verify internet connectivity:
curl https://cloud.langfuse.com/api/public/health - Check for proxy/firewall blocking outbound HTTPS
- If using a custom URL, verify it's correct
Symptom: Langfuse UI shows no traces after conversations
Check:
- Is
TRACE_TO_LANGFUSE=truein~/.claude/settings.json? - Are the API keys correct?
- Check hook logs:
tail -f ~/.claude/state/langfuse_hook.log - Self-hosted: Verify Docker services are running:
docker compose ps - Self-hosted: Test API:
curl http://localhost:3050/api/public/health - Cloud: Test API:
curl https://cloud.langfuse.com/api/public/health
Symptom: Hook execution takes >3 minutes (warning in logs)
Solution:
- Large transcripts can slow processing
- Consider archiving old sessions: move
.jsonlfiles from~/.claude/projects/*/to a backup location - Check Docker resource limits (increase CPU/memory allocation)
Symptom: Services crash due to disk space
Solution:
# Check volume sizes
docker system df -v
# Remove old traces (via Langfuse UI or API)
# Or reset everything:
docker compose down -v
docker compose up -dTypical usage:
- RAM: 4-6GB total (PostgreSQL: 500MB, ClickHouse: 2GB, Redis: 100MB, MinIO: 200MB, Langfuse: 1-2GB)
- Disk: 2-5GB (depends on trace volume and retention)
- CPU: Minimal (spikes during trace ingestion)
Scaling considerations:
- For heavy usage (>1000 traces/day), consider increasing PostgreSQL and ClickHouse memory limits
- ClickHouse benefits from SSD storage for analytics queries
- Redis is used for caching and can be scaled vertically if needed
- langfuse-web: Main web UI and API (port 3050)
- langfuse-worker: Background job processor (handles async tasks)
- postgres: Primary data store (traces, users, projects)
- clickhouse: Analytics database (aggregations, dashboards)
- redis: Cache and job queue
- minio: S3-compatible object storage (media uploads, exports)
- Claude Code writes each message to
~/.claude/projects/<project>/<session>.jsonl - After assistant response, Stop hook triggers
- Hook reads new messages since last execution (tracked in state file)
- Hook groups messages into turns (user → assistant → tools → assistant)
- Each turn becomes a Langfuse trace with nested spans for tool calls
- Langfuse API validates and stores in PostgreSQL
- Background worker processes for ClickHouse analytics
Self-hosted:
- All services run on
localhost(not exposed to network) - Credentials are generated randomly on first setup
.envfile is git-ignored (never commit credentials)- No telemetry is sent to external services
Cloud:
- Traces are sent to Langfuse Cloud (EU or US region)
- API keys are stored in
~/.claude/settings.json(not committed to git) - Review Langfuse's security & privacy documentation for data handling details
Add user ID tracking: Edit the hook to include your name or machine ID in metadata:
metadata={
"user": os.environ.get("USER"),
"hostname": os.uname().nodename,
"source": "claude-code",
}Filter sensitive content: Add a sanitization function to scrub API keys or passwords before sending:
def sanitize(text: str) -> str:
# Remove common secret patterns
text = re.sub(r'sk-[a-zA-Z0-9]{32,}', 'SK-REDACTED', text)
text = re.sub(r'Bearer [a-zA-Z0-9._-]+', 'Bearer REDACTED', text)
return textAdd cost tracking: Estimate token usage based on text length and add to metadata:
import tiktoken
def estimate_tokens(text: str, model: str = "claude") -> int:
# Rough estimate: ~4 chars per token
return len(text) // 4
metadata={
"estimated_tokens": estimate_tokens(user_text + final_output),
}This is a personal project and is not affiliated with, sponsored by, or endorsed by ClickHouse, Inc., Anthropic, or any other organization.
This software is provided "as-is" without warranty of any kind. Use at your own risk. The author makes no guarantees about reliability, security, or fitness for any particular purpose.
No support commitment. While issues and PRs are welcome, there is no guarantee of response time or resolution. This is a side project maintained in spare time.
By using this template, you accept full responsibility for:
- Securing your deployment
- Backing up your data
- Reviewing the code for your specific security requirements
- Any costs, damages, or issues arising from its use
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly (especially the hook script)
- Submit a pull request
Built something with this template? Here's how to go deeper:
- Subscribe to Signal over Noise — Weekly "I Built X" tutorials, AI architecture patterns, and implementation frameworks
- Take the AI Readiness Quiz — 5 questions, 2 minutes. See where your team stands on AI implementation readiness (coming soon)
- Read the full tutorial — The complete walkthrough that inspired this template (18K+ views)
Don De Jesus — Principal AI Architect at ClickHouse. 20+ years building data and AI systems at Deutsche Bank, Verizon, Cloudera, Elastic, and Snowflake. I write about what actually works in production AI, not what sounds good in demos.
MIT License - see LICENSE file for details.
- Langfuse - Open-source LLM observability
- Anthropic Claude - AI assistant platform
Happy tracing! If you find this useful, consider starring the repository.