--- title: "Session Observability & Monitoring" description: "Track Claude Code usage, estimate costs, and identify patterns across development sessions" tags: [observability, guide, performance] keywords: - "monitor claude traffic" - "monitor claude mentions" - "track claude traffic" - "claude code observability" --- # Session Observability & Monitoring > Track Claude Code usage, estimate costs, and identify patterns across your development sessions. ## Table of Contents 1. [Why Monitor Sessions](#why-monitor-sessions) 2. [Session Search & Resume](#session-search--resume) 3. [Setting Up Session Logging](#setting-up-session-logging) 4. [Analyzing Session Data](#analyzing-session-data) 5. [Cost Tracking](#cost-tracking) 6. [Activity Monitoring](#activity-monitoring) 7. [External Monitoring Tools](#external-monitoring-tools) 8. [Proxying Claude Code](#proxying-claude-code) 9. [Patterns & Best Practices](#patterns--best-practices) 10. [Limitations](#limitations) --- ## Why Monitor Sessions Claude Code usage can accumulate quickly, especially in active development. Monitoring helps you: - **Understand costs**: Estimate API spend before invoices arrive - **Identify patterns**: See which tools you use most, which files get edited repeatedly - **Optimize workflow**: Find inefficiencies (e.g., repeatedly reading the same large file) - **Track projects**: Compare usage across different codebases - **Team visibility**: Aggregate usage for team budgeting (when combining logs) --- ## Session Search & Resume After weeks of using Claude Code, finding past conversations becomes challenging. This section covers native options and community tools. ### Native Commands | Command | Use Case | |---------|----------| | `claude -c` / `claude --continue` | Resume most recent session | | `claude -r ` / `claude --resume ` | Resume specific session by ID | | `claude --resume` | Interactive session picker | Sessions are stored locally at `~/.claude/projects//` as JSONL files. ### Community Tools Comparison | Tool | Install | List Speed | Search Speed | Dependencies | Resume Command | |------|---------|------------|--------------|--------------|----------------| | **session-search.sh** (this repo) | Copy script | **10ms** | **400ms** | None (bash) | ✅ Displayed | | claude-conversation-extractor | `pip install` | 230ms | 1.7s | Python | ❌ | | claude-code-transcripts | `uvx` | N/A | N/A | Python | ❌ | | ran CLI | `npm -g` | N/A | Fast | Node.js | ❌ (commands only) | ### Recommended: session-search.sh Zero-dependency bash script optimized for speed with ready-to-use resume commands. **Install:** ```bash cp examples/scripts/session-search.sh ~/.claude/scripts/cs chmod +x ~/.claude/scripts/cs echo "alias cs='~/.claude/scripts/cs'" >> ~/.zshrc source ~/.zshrc ``` **Usage:** ```bash cs # List 10 most recent sessions (~15ms) cs "authentication" # Single keyword search (~400ms) cs "Prisma migration" # Multi-word AND search (both must match) cs -n 20 # Show 20 results cs -p myproject "bug" # Filter by project name cs --since 7d # Sessions from last 7 days cs --since today # Today's sessions only cs --json "api" | jq . # JSON output for scripting cs --rebuild # Force index rebuild ``` **Output:** ``` 2026-01-15 08:32 │ my-project │ Implement OAuth flow for... claude --resume 84287c0d-8778-4a8d-abf1-eb2807e327a8 2026-01-14 21:13 │ other-project │ Fix database migration... claude --resume 1340c42e-eac5-4181-8407-cc76e1a76219 ``` Copy-paste the `claude --resume` command to continue any session. ### How It Works 1. **Index mode** (no filters): Uses cached TSV index. Auto-refreshes when sessions change. ~15ms lookup. 2. **Search mode** (with keyword/filters): Full-text search with 3s timeout. Multi-word queries use AND logic. 3. **Filters**: `--project` (substring match), `--since` (supports `today`, `yesterday`, `7d`, `YYYY-MM-DD`) 4. **Output**: Human-readable by default, `--json` for scripting. Excludes agent/subagent sessions. ### Alternative: Python Tools If you prefer richer features (HTML export, multiple formats): ```bash # Install pip install claude-conversation-extractor # Interactive UI claude-start # Direct search claude-search "keyword" # Export to markdown claude-extract --format markdown ``` See [session-search.sh](../../examples/scripts/session-search.sh) for the complete script. --- ### Session Resume Limitations & Cross-Folder Migration **TL;DR**: Native `--resume` is limited to the current working directory by design. For cross-folder migration, use manual filesystem operations (recommended) or community automation tools (untested). #### Why Resume is Directory-Scoped Claude Code stores sessions at `~/.claude/projects//` where `` is derived from your project's absolute path. For example: - Project at `/home/user/myapp` → Sessions in `~/.claude/projects/-home-user-myapp-/` - Project moved to `/home/user/projects/myapp` → Claude looks for `~/.claude/projects/-home-user-projects-myapp-/` (different directory) **Design rationale**: Sessions store absolute file paths, project-specific context (MCP server configs, `.claudeignore` rules, environment variables). Cross-folder resume would require path rewriting and context validation, which isn't implemented yet. **Related**: GitHub issue [#1516](https://github.com/anthropics/claude-code/issues/1516) tracks community requests for native cross-folder support. #### Manual Migration (Recommended) **When moving a project folder:** ```bash # Before moving project cd ~/.claude/projects/ ls -la # Note the current encoded path # Move your project mv /old/location/myapp /new/location/myapp # Rename session directory to match new path cd ~/.claude/projects/ mv -- -old-location-myapp- -new-location-myapp- # Verify cd /new/location/myapp claude --continue # Should resume successfully ``` **When forking sessions to a new project:** ```bash # Copy session files (preserves original) cd ~/.claude/projects/ cp -n ./-source-project-/*.jsonl ./-target-project-/ # Copy subagents directory if exists if [ -d ./-source-project-/subagents ]; then cp -r ./-source-project-/subagents ./-target-project-/ fi # Resume in target project cd /path/to/target/project claude --continue ``` #### ⚠️ Migration Risks & Caveats **Before migrating sessions, verify compatibility:** | Risk | Impact | Mitigation | |------|--------|------------| | **Hardcoded secrets** | Credentials exposed in new context | Audit `.jsonl` files before migration, redact if needed | | **Absolute paths** | File references break if paths differ | Verify paths exist in target, or accept broken references | | **MCP server configs** | Source MCP servers missing in target | Install matching MCP servers before resuming | | **`.claudeignore` rules** | Different ignore patterns | Review differences, merge if needed | | **Environment variables** | `process.env` context mismatch | Check `.env` files compatibility | **When NOT to migrate sessions:** - Conflicting dependencies (e.g., different Node.js versions, package managers) - Database state differences (migrations applied in source, not in target) - Authentication context (API tokens, OAuth sessions specific to source project) - Security boundaries (migrating from private to public repo) #### Community Automation Tool **claude-migrate-session** by Jim Weller (inspired by Alexis Laporte) automates the manual process above: - **Repository**: [jimweller/dotfiles](https://github.com/jimweller/dotfiles/tree/main/dotfiles/claude-code/skills/claude-migrate-session) - **Features**: Global search with filtering, preserves `.jsonl` + subagents, uses ripgrep for performance - **Status**: Personal dotfiles (0 stars/forks as of Feb 2026), limited adoption - **Command**: `/claude-migrate-session ` **⚠️ Caveat**: This tool has minimal community testing. The manual approach is safer and gives you explicit control over what gets migrated. Test thoroughly before using in production workflows. **Use cases for migration:** - Forking prototype work into production codebase - Moving debugging session to isolated test repository - Continuing architecture discussion in a new project #### Alternative: Entire CLI Session Portability **Native limitation**: Claude Code's `--resume` is tied to absolute file paths, breaking on folder moves. **Entire CLI solution**: Checkpoints are **path-agnostic**, enabling true session portability across project locations. **How it works:** ```bash # In source project cd /old/location/myapp entire capture --agent="claude-code" [... work in Claude Code ...] entire checkpoint --name="migration-complete" # Move project to new location mv /old/location/myapp /new/location/myapp # Resume in target (works because Entire stores relative paths) cd /new/location/myapp entire resume --checkpoint="migration-complete" claude --continue # Resumes with full context ``` **Why Entire checkpoints are portable:** | Aspect | Native `--resume` | Entire CLI | |--------|-------------------|-----------| | **Path storage** | Absolute paths in JSONL | Relative paths in checkpoints | | **Cross-folder** | Breaks (different project encoding) | Works (path-agnostic) | | **Context preservation** | Prompt history only | Prompts + reasoning + file states | | **Agent handoffs** | No | Yes (between Claude/Gemini) | **When to use Entire over manual migration:** - ✅ Frequent project moves/forks - ✅ Multi-agent workflows (Claude → Gemini handoffs) - ✅ Session replay for debugging (rewind to exact state) - ✅ Governance (approval gates on resume) **Trade-off**: Adds tool dependency + storage overhead (~5-10% project size). > **Full docs**: [AI Traceability Guide](./ai-traceability.md#51-entire-cli) --- ### Multi-Agent Orchestration Monitoring For monitoring multiple concurrent Claude Code instances via external orchestrators (Gas Town, multiclaude), see: - **agent-chat** (https://github.com/justinabrahms/agent-chat): Real-time Slack-like UI for agent communications - **Architecture guide**: `guide/ecosystem/ai-ecosystem.md` Section 8.1 - Multi-Agent Orchestration Systems **Architecture pattern** (for custom implementations): 1. Hook logs Task agent spawns: `.claude/hooks/multi-agent-logger.sh` 2. Store in SQLite: `~/.claude/logs/agents.db` (parent_id, child_id, timestamp, task) 3. Stream via SSE: Simple Go/Node HTTP server 4. Dashboard: React/HTML consuming SSE stream **Native Claude Code monitoring** (this guide): - Session search: `session-search.sh` (see [Session Search & Resume](#session-search--resume)) - Activity logs: `session-logger.sh` hook (see [Setting Up Session Logging](#setting-up-session-logging)) - Stats analysis: `session-stats.sh` (see [Analyzing Session Data](#analyzing-session-data)) **When to use external orchestrator monitoring**: - Running Gas Town or multiclaude with 5+ concurrent agents - Need real-time visibility into agent coordination - Debugging orchestration failures (agent conflicts, merge issues) **When native monitoring suffices**: - Single Claude Code session or `--delegate` with <3 subagents - Post-hoc analysis (logs, stats) is enough - Budget/complexity constraints --- ## Setting Up Session Logging ### 1. Install the Logger Hook Copy the session logger to your hooks directory: ```bash # Create hooks directory if needed mkdir -p ~/.claude/hooks # Copy the logger (from this repo's examples) cp examples/hooks/bash/session-logger.sh ~/.claude/hooks/ chmod +x ~/.claude/hooks/session-logger.sh ``` ### 2. Register in Settings Add to `~/.claude/settings.json`: ```json { "hooks": { "PostToolUse": [ { "type": "command", "command": "~/.claude/hooks/session-logger.sh" } ] } } ``` ### 3. Verify Installation Run a few Claude Code commands, then check logs: ```bash ls ~/.claude/logs/ # Should see: activity-2026-01-14.jsonl # View recent entries tail -5 ~/.claude/logs/activity-$(date +%Y-%m-%d).jsonl | jq . ``` ### Configuration Options | Environment Variable | Default | Description | |---------------------|---------|-------------| | `CLAUDE_LOG_DIR` | `~/.claude/logs` | Where to store logs | | `CLAUDE_LOG_TOKENS` | `true` | Enable token estimation | | `CLAUDE_SESSION_ID` | auto-generated | Custom session identifier | --- ## Analyzing Session Data ### Using session-stats.sh ```bash # Copy the script cp examples/scripts/session-stats.sh ~/.local/bin/ chmod +x ~/.local/bin/session-stats.sh # Today's summary session-stats.sh # Last 7 days session-stats.sh --range week # Specific date session-stats.sh --date 2026-01-14 # Filter by project session-stats.sh --project my-app # Machine-readable output session-stats.sh --json ``` ### Sample Output ``` ═══════════════════════════════════════════════════════════ Claude Code Session Statistics - today ═══════════════════════════════════════════════════════════ Summary Total operations: 127 Sessions: 3 Token Usage Input tokens: 45,230 Output tokens: 12,450 Total tokens: 57,680 Estimated Cost (Sonnet rates) Input: $0.1357 Output: $0.1868 Total: $0.3225 Tools Used Edit: 45 Read: 38 Bash: 24 Grep: 12 Write: 8 Projects my-app: 89 other-project: 38 ``` ### Reading for Quality, Not Just Quantity Token counts tell you how much you used Claude Code. JSONL logs can also tell you **how well your configuration is working** — if you know what to look for. Beyond cost metrics, three patterns reliably signal that a skill, rule, or CLAUDE.md section needs updating: **Repeated reads of the same file** If Claude reads the same file 3+ times in one session, the content it needs probably isn't where it expects to find it. Consider moving the relevant context into a skill or CLAUDE.md section. ```bash # Files read more than 3x in recent sessions jq -r 'select(.tool == "Read") | .file' ~/.claude/logs/activity-*.jsonl \ | sort | uniq -c | sort -rn | awk '$1 > 3' ``` **Tool failures on the same command** A Bash command that fails repeatedly across sessions usually means a skill has an outdated path, renamed binary, or command that no longer works with your current stack. ```bash # Failing commands jq -r 'select(.tool == "Bash" and (.exit_code // 0) != 0) | .command' \ ~/.claude/logs/activity-*.jsonl | sort | uniq -c | sort -rn | head -10 ``` **High edit frequency on the same file** Files edited heavily across sessions often indicate missing context — the file's purpose isn't clear to the agent, or conventions around it aren't documented. ```bash # Most-edited files (proxy for context gaps) jq -r 'select(.tool == "Edit") | .file' ~/.claude/logs/activity-*.jsonl \ | sort | uniq -c | sort -rn | head -10 ``` For each pattern you surface, ask: is there a skill, rule, or CLAUDE.md section that should cover this? See [§9.23 Configuration Lifecycle & The Update Loop](#923-configuration-lifecycle-the-update-loop) for the full workflow. --- ### Log Format Each log entry is a JSON object: ```json { "timestamp": "2026-01-14T15:30:00Z", "session_id": "1705234567-12345", "tool": "Edit", "file": "src/components/Button.tsx", "project": "my-app", "tokens": { "input": 350, "output": 120, "total": 470 } } ``` --- ## Cost Tracking ### Token Estimation Method The logger estimates tokens using a simple heuristic: **~4 characters per token**. This is approximate and tends to slightly overestimate. ### Cost Rates Default rates are for Claude Sonnet. Adjust via environment variables: ```bash # Sonnet rates (default) export CLAUDE_RATE_INPUT=0.003 # $3/1M tokens export CLAUDE_RATE_OUTPUT=0.015 # $15/1M tokens # Opus rates (if using Opus) export CLAUDE_RATE_INPUT=0.015 # $15/1M tokens export CLAUDE_RATE_OUTPUT=0.075 # $75/1M tokens # Haiku rates export CLAUDE_RATE_INPUT=0.00025 # $0.25/1M tokens export CLAUDE_RATE_OUTPUT=0.00125 # $1.25/1M tokens ``` ### Budget Alerts (Manual Pattern) Add to your shell profile for daily budget warnings: ```bash # ~/.zshrc or ~/.bashrc claude_budget_check() { local cost=$(session-stats.sh --json 2>/dev/null | jq -r '.summary.estimated_cost.total // 0') local threshold=5.00 # $5 daily budget if (( $(echo "$cost > $threshold" | bc -l) )); then echo "⚠️ Claude Code daily spend: \$$cost (threshold: \$$threshold)" fi } # Run on shell start claude_budget_check ``` --- ## Activity Monitoring Cost tracking tells you *how much* you spend. Activity monitoring tells you *what Claude Code actually did*: which files it read, which commands it ran, which URLs it fetched. This is the audit layer. ### Session JSONL: The Ground Truth Every tool call Claude Code makes is recorded in the session JSONL files at `~/.claude/projects//`. Each entry with `type: "assistant"` contains a `content` array where `type: "tool_use"` blocks document every action. ```bash # Find your session files ls ~/.claude/projects/-$(pwd | tr '/' '-')-/ # Inspect tool calls in a session cat ~/.claude/projects/-your-project-/SESSION_ID.jsonl | \ jq 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | {tool: .name, input: .input}' ``` ### What Tool Calls Reveal | Tool | What It Exposes | |------|----------------| | `Read` | Files accessed (path, line range) | | `Write` / `Edit` | Files modified (path, content delta) | | `Bash` | Commands executed (full command string) | | `WebFetch` | URLs fetched (may include data sent in POST) | | `Task` | Subagent spawns (prompt passed to sub-model) | | `Glob` / `Grep` | Search patterns and scope | ### Practical Audit Queries ```bash # All files read in a session SESSION=~/.claude/projects/-your-project-/SESSION_ID.jsonl jq 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use" and .name == "Read") | .input.file_path' "$SESSION" # All bash commands executed jq 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use" and .name == "Bash") | .input.command' "$SESSION" # All URLs fetched jq 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use" and .name == "WebFetch") | .input.url' "$SESSION" # Count tool usage by type jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | .name' "$SESSION" | sort | uniq -c | sort -rn ``` ### Sensitive Patterns to Watch These tool call patterns are worth flagging in automated audits: | Pattern | Risk | Detection | |---------|------|-----------| | `Read` on `.env`, `*.pem`, `id_rsa` | Credential access | `jq '... | select(.input.file_path | test("\\.(env|pem|key)$"))'` | | `Bash` with `rm -rf`, `git push --force` | Destructive action | `jq '... | select(.input.command | test("rm -rf\|force-push"))'` | | `WebFetch` on external URLs | Data exfiltration risk | `jq '... | select(.name == "WebFetch") | .input.url'` | | `Write` on files outside project root | Scope creep | Check paths against working directory | > **Security context**: Claude Code operates read-write on your filesystem with your user permissions. The JSONL audit trail is your record of what happened. For teams, consider syncing these logs to immutable storage. --- ## External Monitoring Tools Beyond the hook-based approach above, the community has built purpose-specific tools. This is a factual snapshot as of early 2026. **Instrumenting the coding assistant itself, not just the application it produces, is what turns AI usage into something auditable.** Tracing every decision an agent makes, including tools like Claude Code and Codex, with OpenTelemetry closes the gap between "the AI probably did the right thing" and having the exact chain of tool calls that produced a given output. A system that cannot be observed at that level cannot be structurally trusted with real autonomy. *Annie Freeman, Devoxx, 2026, talk "Un-observable AI is un-trustworthy AI"* | Tool | Type | What It Does | Install | |------|------|-------------|---------| | **ccusage** | CLI / TUI | Cost tracking from JSONL — the de-facto reference for pricing data. ~10K GitHub stars. | `npm i -g ccusage` | | **claude-code-otel** | OpenTelemetry exporter | Emits spans to any OTEL collector. Integrates with Prometheus + Grafana dashboards. Enterprise-focused. | `npm i -g claude-code-otel` | | **Akto** | SaaS / self-hosted | API security guardrails + audit trail. Intercepts at the API level, flags policy violations. | [akto.io](https://akto.io) | | **MLflow Tracing** | CLI + SDK | Exact token counts, tool spans, LLM-as-judge evaluation. CLI mode: zero Python required. Best for ML/MLOps teams. | `pip install mlflow` → [see section below](#mlflow-tracing) | | **ccboard** | TUI + Web | Unified dashboard for sessions, costs, stats. Activity/audit tab in development. | `cargo install ccboard` | | **claude-crusts** | CLI | One-command context pollution scanner: flags stale files, oversized memories, and redundant rule loading. Spot what's inflating your context before running sessions. | [github.com/Abinesh-L/claude-crusts](https://github.com/Abinesh-L/claude-crusts) | ### Decision Guide ``` Want cost numbers fast? → ccusage (CLI, 0 config) Need enterprise audit trail? → claude-code-otel + Grafana or Akto Already using MLflow for ML? → MLflow tracing integration (see below) Need agent regression detection? → MLflow tracing + LLM-as-judge Want a persistent TUI/Web UI? → ccboard Context pollution audit? → claude-crusts (1-command scan) ``` ### ccusage ```bash npm i -g ccusage ccusage # Today's usage ccusage --days 7 # Last 7 days ``` Reads directly from `~/.claude/projects/**/*.jsonl`. No API keys, no data sent externally. Source: [github.com/ryoppippi/ccusage](https://github.com/ryoppippi/ccusage). ### claude-code-otel Exports Claude Code activity as OpenTelemetry spans: ```bash npm i -g claude-code-otel claude-code-otel --collector http://localhost:4318 ``` Spans include tool name, duration, token counts. Plug into any OTEL-compatible backend (Jaeger, Tempo, Datadog). Source: [github.com/badger-99/claude-code-otel](https://github.com/badger-99/claude-code-otel). ### ccboard ```bash cargo install ccboard ccboard # Launch TUI ccboard --web # Launch Web UI (localhost:3000) ``` Source: [github.com/FlorianBruniaux/ccboard](https://github.com/FlorianBruniaux/ccboard). An Activity tab covering file access, bash commands, and network calls is planned (see `docs/resource-evaluations/ccboard-activity-module-plan.md`). ### MLflow Tracing **When to use**: Teams already in the MLflow/MLOps ecosystem, or anyone needing exact token counts + LLM-based quality evaluation. Not the right fit for solo devs wanting quick cost numbers (use ccusage instead). **What makes it different from the other tools**: MLflow intercepts at the API level, not post-hoc from JSONL. It captures **exact** token counts (vs the ~15-25% variance of hook-based estimation) and enables **LLM-as-judge** regression detection — not just "what happened" but "was it good?". #### Setup: CLI mode (no Python required) Works with interactive `claude` sessions. Hooks into `.claude/settings.json`: ```bash pip install "mlflow[genai]>=3.4" # Enable tracing in current project directory mlflow autolog claude # With custom backend (recommended for persistence) mlflow autolog claude -u sqlite:///mlflow.db # With named experiment mlflow autolog claude -n "my-project" # Check status / disable mlflow autolog claude --status mlflow autolog claude --disable ``` Launch the UI to inspect traces: ```bash mlflow server # → http://localhost:5000 ``` **What gets captured automatically**: user prompts, assistant responses, tool calls (name + inputs + outputs), token counts (exact), latency per call, session metadata. #### Setup: SDK mode (Python agents) ```python import mlflow mlflow.anthropic.autolog() # one line, before anything else mlflow.set_experiment("my-agent") # Use ClaudeSDKClient normally — all interactions are traced # ⚠️ Only ClaudeSDKClient is supported. Direct API calls are not traced. from anthropic import claude_agent_sdk async with ClaudeSDKClient(options=AGENT_OPTIONS) as client: await client.query(query) ``` Requires: `mlflow>=3.5` + `claude-agent-sdk>=0.1.0`. #### MCP server: bidirectional integration Claude Code can query its own traces directly. Add to `.claude/settings.json`: ```json { "mcpServers": { "mlflow-mcp": { "command": "uv", "args": ["run", "--with", "mlflow[mcp]>=3.5.1", "mlflow", "mcp", "run"], "env": { "MLFLOW_TRACKING_URI": "" } } } } ``` Once configured, you can ask Claude Code: *"Find all sessions where the backend-architect agent used more than 20 tool calls"* — it queries MLflow directly without copy-pasting IDs. #### LLM-as-judge: agent regression detection The key capability absent from all other tools in this section. After modifying an agent's instructions, measure whether quality improved or degraded: ```python from mlflow.genai.scorers import scorer, ConversationCompleteness, RelevanceToQuery from mlflow.entities.model_registry import Feedback @scorer def tool_efficiency(trace) -> int: """Count tool calls — lower is better for well-scoped tasks.""" return len(trace.search_spans(span_type="TOOL")) @scorer def permission_blocks(trace) -> int: """Detect how often the agent was blocked by permission gates.""" return sum( 1 for span in trace.search_spans(span_type="TOOL") if span.outputs and "requires approval" in str(span.outputs).lower() ) # Run evaluation against recorded traces traces = mlflow.search_traces(experiment_ids=[""], max_results=50) results = mlflow.genai.evaluate( data=traces, scorers=[ tool_efficiency, permission_blocks, ConversationCompleteness(), RelevanceToQuery(), ] ) ``` **Built-in scorers**: `ConversationCompleteness`, `RelevanceToQuery`, `UserFrustration`, `SafetyScorer`. **Custom scorers**: full access to the trace object (all spans, inputs, outputs, token counts). #### Limitations | Limitation | Detail | |------------|--------| | **CLI mode audience** | Best for interactive sessions; SDK mode required for programmatic agents | | **SDK restriction** | Only `ClaudeSDKClient` — direct API calls bypass tracing | | **PII risk** | Traces capture full conversation content. Redact before storing if working with sensitive data | | **Production backend** | SQLite = dev only. Use PostgreSQL/MySQL for production | | **OpenTelemetry** | MLflow 3.6+ exports to any OTEL-compatible backend (Datadog, Grafana, etc.) | --- ## Proxying Claude Code A common question: "Can I run Proxyman/Charles to see what Claude Code sends to Anthropic?" **Short answer**: Not directly. Here's why, and what works instead. ### Why System Proxies Don't Work Claude Code is a Node.js process. By default, Node.js ignores system-level proxy settings (`HTTP_PROXY`, `HTTPS_PROXY`) — it uses its own TLS stack and doesn't read macOS/Windows proxy configurations. Additionally, even if traffic flows through your proxy, the TLS certificate mismatch causes Claude Code to fail (`CERT_UNTRUSTED`). ### Option 1: Trust a MITM Certificate (Proxyman / Charles) Force Node.js to trust your proxy's CA certificate: ```bash # Export Proxyman's CA cert (File → Export → Root Certificate) # Then point Node.js at it: export NODE_EXTRA_CA_CERTS="/path/to/proxyman-ca.pem" # Start Claude Code — traffic will now route through Proxyman claude ``` Same approach works for Charles: `Help → SSL Proxying → Export Charles Root Certificate`. **Caveats**: - Some Claude Code versions use certificate pinning for `api.anthropic.com` — this may still fail - This approach requires a running Proxyman/Charles instance listening on the configured port ### Option 2: Redirect API Traffic with ANTHROPIC_API_URL Point Claude Code at a local interceptor instead of `api.anthropic.com`: ```bash export ANTHROPIC_API_URL="http://localhost:8080" claude ``` Run any HTTP proxy/logger on port 8080 that forwards to `https://api.anthropic.com`. This bypasses TLS entirely for the Claude Code → proxy hop. **Use cases**: Logging request payloads, injecting headers, rate-limiting locally, replaying requests. ### Option 3: mitmproxy (Recommended) [mitmproxy](https://mitmproxy.org) is the cleanest open-source solution. It provides a scriptable HTTPS proxy with a web UI and terminal interface. ```bash # Install brew install mitmproxy # macOS # or: pip install mitmproxy # Start transparent proxy on port 8080 mitmproxy --listen-port 8080 # In a new terminal, point Claude Code at it export NODE_EXTRA_CA_CERTS="$(python3 -c 'import mitmproxy.certs; print(mitmproxy.certs.Cert.default_ca_path())')" export HTTPS_PROXY="http://localhost:8080" claude ``` The mitmproxy web UI (`mitmweb`) at `http://localhost:8081` shows full request/response bodies — including the JSON payloads Claude Code sends to Anthropic. **What you'll see**: System prompt, user messages, tool definitions, tool results, model parameters. ### Option 4: Minimal Python Logging Proxy For a zero-dependency approach: ```python # proxy.py — simple HTTPS logging proxy from http.server import HTTPServer, BaseHTTPRequestHandler import urllib.request, json, sys TARGET = "https://api.anthropic.com" class LoggingProxy(BaseHTTPRequestHandler): def do_POST(self): length = int(self.headers["Content-Length"]) body = self.rfile.read(length) print(json.dumps(json.loads(body), indent=2)) # Log request # Forward to Anthropic... HTTPServer(("localhost", 8080), LoggingProxy).serve_forever() ``` ```bash python3 proxy.py & export ANTHROPIC_API_URL="http://localhost:8080" claude ``` > **Privacy note**: Proxied traffic includes everything in the conversation context — file contents Claude has read, your code, any secrets it encountered. Handle proxy logs accordingly. --- ## Patterns & Best Practices ### 1. Weekly Review Set a calendar reminder to review weekly stats: ```bash session-stats.sh --range week ``` Look for: - Unusually high token usage days - Repeated operations on same files (inefficiency signal) - Project distribution (where time is spent) ### 2. Per-Project Tracking Use `CLAUDE_SESSION_ID` to tag sessions by project: ```bash export CLAUDE_SESSION_ID="project-myapp-$(date +%s)" claude ``` ### 3. Team Aggregation For team-wide tracking, sync logs to shared storage: ```bash # Example: sync to S3 daily aws s3 sync ~/.claude/logs/ s3://company-claude-logs/$(whoami)/ ``` Then aggregate with: ```bash # Download all team logs aws s3 sync s3://company-claude-logs/ /tmp/team-logs/ # Combine and analyze cat /tmp/team-logs/*/activity-$(date +%Y-%m-%d).jsonl | \ jq -s 'group_by(.project) | map({project: .[0].project, total_tokens: [.[].tokens.total] | add})' ``` ### 4. Log Rotation Logs accumulate over time. Add cleanup to cron: ```bash # Clean logs older than 30 days find ~/.claude/logs -name "*.jsonl" -mtime +30 -delete ``` ### 5. Resilience Testing for AI Dependencies **Deliberately simulate AI unavailability or degraded output to confirm the team can keep working without it.** Provider outages, extreme latency, and low-quality responses are failure modes worth rehearsing on purpose, the same way chaos engineering rehearses server or network failures. A workflow that silently assumes the AI is always available and always correct only gets that assumption tested for the first time during an actual incident. *Konstantin Pavlov, Devoxx, 2025* --- ## Limitations ### What This Monitoring CANNOT Do | Limitation | Reason | |------------|--------| | **Exact token counts** | Claude Code CLI doesn't expose API token metrics | | **TTFT (Time to First Token)** | Hook runs after tool completes, not during streaming | | **Real-time streaming metrics** | No hook event during response generation | | **Actual API costs** | Token estimates are heuristic, not from billing | | **Model selection** | Log doesn't capture which model was used per request | | **Context window usage** | No visibility into current context percentage | ### Accuracy Notes - **Token estimates**: ~15-25% variance from actual billing - **Cost estimates**: Use as directional guidance, not accounting - **Session boundaries**: Sessions are approximated by ID, not exact API sessions ### What You CAN Trust - **Tool usage counts**: Exact count of each tool invocation - **File access patterns**: Which files were touched - **Relative comparisons**: Day-to-day/project-to-project trends - **Operation timing**: When tools were used (timestamp) --- --- ## Manager Audit Checklist For engineering managers and team leads who need to verify Claude Code is being used appropriately within their team, these are the practical audit queries. ### Weekly Spot Check (5 minutes) ```bash # Did anything unusual happen this week? # 1. Files accessed outside project scope find ~/.claude/projects/ -name "*.jsonl" -newer "$(date -d '7 days ago' +%Y-%m-%d 2>/dev/null || date -v-7d +%Y-%m-%d)" 2>/dev/null | \ xargs jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use" and .name == "Read") | .input.file_path' 2>/dev/null | \ grep -v "^$(pwd)" | sort -u # 2. Destructive commands run find ~/.claude/projects/ -name "*.jsonl" -newer "$(date -d '7 days ago' +%Y-%m-%d 2>/dev/null || date -v-7d +%Y-%m-%d)" 2>/dev/null | \ xargs jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use" and .name == "Bash") | .input.command' 2>/dev/null | \ grep -iE "(drop|delete|truncate|rm -rf|git push --force)" ``` ### Compliance Reporting For regulated environments, generate a summary of AI activity for auditors: ```bash #!/bin/bash # Monthly compliance report for Claude Code activity START_DATE=${1:-$(date -d '30 days ago' +%Y-%m-%d 2>/dev/null || date -v-30d +%Y-%m-%d)} END_DATE=${2:-$(date +%Y-%m-%d)} REPORT_FILE="ai-activity-report-${START_DATE}-${END_DATE}.json" echo "Generating compliance report: $START_DATE to $END_DATE" # Count sessions, tool calls, and file accesses jq -s '{ report_period: {start: "'"$START_DATE"'", end: "'"$END_DATE"'"}, tool_usage: (group_by(.tool) | map({tool: .[0].tool, count: length})), unique_files_accessed: ([.[].file] | unique | length), sessions: ([.[].session_id] | unique | length) }' ~/.claude/logs/activity-*.jsonl 2>/dev/null > "$REPORT_FILE" || \ echo "No activity logs found. Set up session-logger.sh hook to enable." echo "Report saved: $REPORT_FILE" ``` For a full governance setup with automatic audit trail logging, see [Enterprise AI Governance §6.2](../security/enterprise-governance.md#62-audit-trail-setup). --- ## 10. Team-level Log Aggregation The sections above cover individual developer monitoring. For a team of 10+ developers, you need logs flowing into a central store to track total spend, flag unusual patterns, and answer compliance questions. ### Option A: Route Through LiteLLM Gateway (Recommended) The [API Gateway guide](./api-gateway.md) captures usage at the network level with zero client-side setup: every request is logged server-side with the team's virtual key alias, model, and token counts. Each developer connects to the same gateway with a team-scoped virtual key. No per-machine cron jobs. This is the lower-friction path for most teams. The session JSONL approach below is useful when you need file-level detail (which files were read and written) rather than just token usage. ### Option B: Ship Session JSONL to a Central Store Claude Code writes session logs to `~/.claude/projects/**/*.jsonl`. A lightweight cron job or PostToolUse hook can ship new entries to Loki: ```bash #!/bin/bash # Ship new session log entries to Loki # Add to crontab: */5 * * * * ~/.claude/hooks/ship-logs.sh LOKI_ENDPOINT="${CENTRAL_LOG_ENDPOINT:-http://loki:3100/loki/api/v1/push}" TEAM_ID="${CLAUDE_TEAM_ID:-unknown}" MARKER="/tmp/.claude-last-ship-${USER}" find ~/.claude/projects -name "*.jsonl" -newer "$MARKER" 2>/dev/null | while read -r f; do while IFS= read -r line; do curl -s -X POST "$LOKI_ENDPOINT" \ -H "Content-Type: application/json" \ -d "{\"streams\":[{\"stream\":{\"app\":\"claude_code\",\"team\":\"$TEAM_ID\",\"user\":\"$(whoami)\"},\"values\":[[\"$(date +%s%N)\",$(echo "$line" | jq -Rs .)]}]}" \ > /dev/null done < "$f" done touch "$MARKER" ``` Set `CLAUDE_TEAM_ID` and `CENTRAL_LOG_ENDPOINT` in each developer's shell profile. Coordinate with your platform team to provision the Loki endpoint. ### OTel Collector Configuration If you are using the LiteLLM Gateway with OTel export, a minimal collector config to forward spans to Grafana Tempo: ```yaml receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 processors: batch: timeout: 5s exporters: otlp: endpoint: tempo:4317 tls: insecure: true service: pipelines: traces: receivers: [otlp] processors: [batch] exporters: [otlp] ``` This feeds Claude Code usage spans into the same Tempo backend as your application traces, so you can correlate an AI session with a deployment event on a single timeline. ### Key Grafana Queries With LiteLLM Prometheus metrics and Loki for session logs, three panels cover most team needs: ```promql # Daily spend by team (USD) sum by (team_id) (increase(litellm_spend_total[1d])) # Request volume by model sum by (model) (rate(litellm_requests_total[1h])) # Budget headroom (fraction remaining per team) 1 - (litellm_spend_total / litellm_budget_limit) ``` --- ## Related Resources - [Session Search Script](../../examples/scripts/session-search.sh) - Fast session search & resume - [Session Logger Hook](../../examples/hooks/bash/session-logger.sh) - [Stats Analysis Script](../../examples/scripts/session-stats.sh) - [Enterprise AI Governance](../security/enterprise-governance.md) - Org-level governance, audit trails, compliance - [Third-Party Tools](../ecosystem/third-party-tools.md) - Community GUIs, TUIs, and dashboards (ccusage, ccburn, claude-code-viewer) - [Data Privacy Guide](../security/data-privacy.md) - What data leaves your machine - [Cost Optimization](#cost-optimization-tips) - Tips to reduce spend