Real-time monitoring and visualization for Claude Code agents through comprehensive hook event tracking. You can watch the full breakdown here.
This system provides complete observability into Claude Code agent behavior by capturing, storing, and visualizing Claude Code Hook events in real-time. It enables monitoring of multiple concurrent agents with session tracking, event filtering, and live updates.
Claude Agents → Hook Scripts → HTTP POST → Bun Server → SQLite → WebSocket → Vue Client
Before getting started, ensure you have the following installed:
- Claude Code - Anthropic's official CLI for Claude
- Astral uv - Fast Python package manager (required for hook scripts)
- Bun, npm, or yarn - For running the server and client
- Anthropic API Key - Set as
ANTHROPIC_API_KEYenvironment variable - OpenAI API Key (optional) - For multi-model support with just-prompt MCP tool
- ElevenLabs API Key (optional) - For audio features
To setup observability in your repo,we need to copy the .claude directory to your project root.
To integrate the observability hooks into your projects:
-
Copy the entire
.claudedirectory to your project root:cp -R .claude /path/to/your/project/
-
Update the
settings.jsonconfiguration:Open
.claude/settings.jsonin your project and modify thesource-appparameter to identify your project:{ "hooks": { "PreToolUse": [{ "matcher": "", "hooks": [ { "type": "command", "command": "uv run .claude/hooks/pre_tool_use.py" }, { "type": "command", "command": "uv run .claude/hooks/send_event.py --source-app YOUR_PROJECT_NAME --event-type PreToolUse --summarize" } ] }], "PostToolUse": [{ "matcher": "", "hooks": [ { "type": "command", "command": "uv run .claude/hooks/post_tool_use.py" }, { "type": "command", "command": "uv run .claude/hooks/send_event.py --source-app YOUR_PROJECT_NAME --event-type PostToolUse --summarize" } ] }], "UserPromptSubmit": [{ "hooks": [ { "type": "command", "command": "uv run .claude/hooks/user_prompt_submit.py --log-only" }, { "type": "command", "command": "uv run .claude/hooks/send_event.py --source-app YOUR_PROJECT_NAME --event-type UserPromptSubmit --summarize" } ] }] // ... (similar patterns for Notification, Stop, SubagentStop, PreCompact) } }Replace
YOUR_PROJECT_NAMEwith a unique identifier for your project (e.g.,my-api-server,react-app, etc.). -
Ensure the observability server is running:
# From the observability project directory (this codebase) ./scripts/start-system.sh
Now your project will send events to the observability system whenever Claude Code performs actions.
You can quickly view how this works by running this repositories .claude setup.
# 1. Start both server and client
./scripts/start-system.sh
# 2. Open http://localhost:5173 in your browser
# 3. Open Claude Code and run the following command:
Run git ls-files to understand the codebase.
# 4. Watch events stream in the client
# 5. Copy the .claude folder to other projects you want to emit events from.
cp -R .claude <directory of your codebase you want to emit events from>claude-code-hooks-multi-agent-observability/
│
├── apps/ # Application components
│ ├── server/ # Bun TypeScript server
│ │ ├── src/
│ │ │ ├── index.ts # Main server with HTTP/WebSocket endpoints
│ │ │ ├── db.ts # SQLite database management & migrations
│ │ │ └── types.ts # TypeScript interfaces
│ │ ├── package.json
│ │ └── events.db # SQLite database (gitignored)
│ │
│ └── client/ # Vue 3 TypeScript client
│ ├── src/
│ │ ├── App.vue # Main app with theme & WebSocket management
│ │ ├── components/
│ │ │ ├── EventTimeline.vue # Event list with auto-scroll
│ │ │ ├── EventRow.vue # Individual event display
│ │ │ ├── FilterPanel.vue # Multi-select filters
│ │ │ ├── ChatTranscriptModal.vue # Chat history viewer
│ │ │ ├── StickScrollButton.vue # Scroll control
│ │ │ └── LivePulseChart.vue # Real-time activity chart
│ │ ├── composables/
│ │ │ ├── useWebSocket.ts # WebSocket connection logic
│ │ │ ├── useEventColors.ts # Color assignment system
│ │ │ ├── useChartData.ts # Chart data aggregation
│ │ │ └── useEventEmojis.ts # Event type emoji mapping
│ │ ├── utils/
│ │ │ └── chartRenderer.ts # Canvas chart rendering
│ │ └── types.ts # TypeScript interfaces
│ ├── .env.sample # Environment configuration template
│ └── package.json
│
├── .claude/ # Claude Code integration
│ ├── hooks/ # Hook scripts (Python with uv)
│ │ ├── send_event.py # Universal event sender
│ │ ├── pre_tool_use.py # Tool validation & blocking
│ │ ├── post_tool_use.py # Result logging
│ │ ├── notification.py # User interaction events
│ │ ├── user_prompt_submit.py # User prompt logging & validation
│ │ ├── stop.py # Session completion
│ │ └── subagent_stop.py # Subagent completion
│ │
│ └── settings.json # Hook configuration
│
├── scripts/ # Utility scripts
│ ├── start-system.sh # Launch server & client
│ ├── reset-system.sh # Stop all processes
│ └── test-system.sh # System validation
│
└── logs/ # Application logs (gitignored)
If you want to master claude code hooks watch this video
The hook system intercepts Claude Code lifecycle events:
-
send_event.py: Core script that sends event data to the observability server- Supports
--add-chatflag for including conversation history - Validates server connectivity before sending
- Handles all event types with proper error handling
- Supports
-
Event-specific hooks: Each implements validation and data extraction
pre_tool_use.py: Blocks dangerous commands, validates tool usagepost_tool_use.py: Captures execution results and outputsnotification.py: Tracks user interaction pointsuser_prompt_submit.py: Logs user prompts, supports validation (v1.0.54+)stop.py: Records session completion with optional chat historysubagent_stop.py: Monitors subagent task completion
Bun-powered TypeScript server with real-time capabilities:
- Database: SQLite with WAL mode for concurrent access
- Endpoints:
POST /events- Receive events from agentsGET /events/recent- Paginated event retrieval with filteringGET /events/filter-options- Available filter valuesWS /stream- Real-time event broadcasting
- Features:
- Automatic schema migrations
- Event validation
- WebSocket broadcast to all clients
- Chat transcript storage
Vue 3 application with real-time visualization:
-
Visual Design:
- Dual-color system: App colors (left border) + Session colors (second border)
- Gradient indicators for visual distinction
- Dark/light theme support
- Responsive layout with smooth animations
-
Features:
- Real-time WebSocket updates
- Multi-criteria filtering (app, session, event type)
- Live pulse chart with session-colored bars and event type indicators
- Time range selection (1m, 3m, 5m) with appropriate data aggregation
- Chat transcript viewer with syntax highlighting
- Auto-scroll with manual override
- Event limiting (configurable via
VITE_MAX_EVENTS_TO_DISPLAY)
-
Live Pulse Chart:
- Canvas-based real-time visualization
- Session-specific colors for each bar
- Event type emojis displayed on bars
- Smooth animations and glow effects
- Responsive to filter changes
- Event Generation: Claude Code executes an action (tool use, notification, etc.)
- Hook Activation: Corresponding hook script runs based on
settings.jsonconfiguration - Data Collection: Hook script gathers context (tool name, inputs, outputs, session ID)
- Transmission:
send_event.pysends JSON payload to server via HTTP POST - Server Processing:
- Validates event structure
- Stores in SQLite with timestamp
- Broadcasts to WebSocket clients
- Client Update: Vue app receives event and updates timeline in real-time
| Event Type | Emoji | Purpose | Color Coding | Special Display |
|---|---|---|---|---|
| PreToolUse | 🔧 | Before tool execution | Session-based | Tool name & details |
| PostToolUse | ✅ | After tool completion | Session-based | Tool name & results |
| Notification | 🔔 | User interactions | Session-based | Notification message |
| Stop | 🛑 | Response completion | Session-based | Summary & chat transcript |
| SubagentStop | 👥 | Subagent finished | Session-based | Subagent details |
| PreCompact | 📦 | Context compaction | Session-based | Compaction details |
| UserPromptSubmit | 💬 | User prompt submission | Session-based | Prompt: "user message" (italic) |
The UserPromptSubmit hook captures every user prompt before Claude processes it. In the UI:
- Displays as
Prompt: "user's message"in italic text - Shows the actual prompt content inline (truncated to 100 chars)
- Summary appears on the right side when AI summarization is enabled
- Useful for tracking user intentions and conversation flow
-
Copy the event sender:
cp .claude/hooks/send_event.py YOUR_PROJECT/.claude/hooks/
-
Add to your
.claude/settings.json:{ "hooks": { "PreToolUse": [{ "matcher": ".*", "hooks": [{ "type": "command", "command": "uv run .claude/hooks/send_event.py --source-app YOUR_APP --event-type PreToolUse" }] }] } }
Already integrated! Hooks run both validation and observability:
{
"type": "command",
"command": "uv run .claude/hooks/pre_tool_use.py"
},
{
"type": "command",
"command": "uv run .claude/hooks/send_event.py --source-app cc-hooks-observability --event-type PreToolUse"
}# System validation
./scripts/test-system.sh
# Manual event test
curl -X POST http://localhost:4000/events \
-H "Content-Type: application/json" \
-d '{
"source_app": "test",
"session_id": "test-123",
"hook_event_type": "PreToolUse",
"payload": {"tool_name": "Bash", "tool_input": {"command": "ls"}}
}'Copy .env.sample to .env in the project root and fill in your API keys:
Application Root (.env file):
ANTHROPIC_API_KEY– Anthropic Claude API key (required)ENGINEER_NAME– Your name (for logging/identification)GEMINI_API_KEY– Google Gemini API key (optional)OPENAI_API_KEY– OpenAI API key (optional)ELEVEN_API_KEY– ElevenLabs API key (optional)
Server (.env file in apps/server/.env):
SERVER_HOST=localhost– Network interface to bind to (default: localhost)- Use
localhostfor local access only - Use
0.0.0.0to listen on all network interfaces - Use specific IP address (e.g.,
192.168.1.100) for specific interface
- Use
SERVER_PORT=4000– Port for HTTP/WebSocket server (default: 4000)CORS_ORIGIN=*– CORS origin configuration (default: * for all origins)
Client (.env file in apps/client/.env):
VITE_SERVER_HOST=localhost– Server host to connect to (default: localhost)VITE_SERVER_PORT=4000– Server port to connect to (default: 4000)VITE_MAX_EVENTS_TO_DISPLAY=100– Maximum events to show (removes oldest when exceeded)
The observability server now supports flexible network binding:
-
Local-only access (default):
# In apps/server/.env SERVER_HOST=localhost SERVER_PORT=4000 -
Network-wide access:
# In apps/server/.env SERVER_HOST=0.0.0.0 SERVER_PORT=4000 -
Custom configuration:
# Start with environment variables SERVER_HOST=0.0.0.0 SERVER_PORT=8080 ./scripts/start-system.sh
When the server binds to 0.0.0.0, the start script will display both local and network access URLs.
- Server:
4000(HTTP/WebSocket) - configurable viaSERVER_PORT - Client:
5173(Vite dev server)
- Blocks dangerous commands (
rm -rf, etc.) - Prevents access to sensitive files (
.env, private keys) - Validates all inputs before execution
- No external dependencies for core functionality
- Server: Bun, TypeScript, SQLite
- Client: Vue 3, TypeScript, Vite, Tailwind CSS
- Hooks: Python 3.8+, Astral uv, TTS (ElevenLabs or OpenAI), LLMs (Claude or OpenAI)
- Communication: HTTP REST, WebSocket
If your hook scripts aren't executing properly, it might be due to relative paths in your .claude/settings.json. Claude Code documentation recommends using absolute paths for command scripts.
Solution: Use the custom Claude Code slash command to automatically convert all relative paths to absolute paths:
# In Claude Code, simply run:
/convert_paths_absoluteThis command will:
- Find all relative paths in your hook command scripts
- Convert them to absolute paths based on your current working directory
- Create a backup of your original settings.json
- Show you exactly what changes were made
This ensures your hooks work correctly regardless of where Claude Code is executed from.
And prepare for Agentic Engineering
Learn to code with AI with foundational Principles of AI Coding
Follow the IndyDevDan youtube channel for more AI coding tips and tricks.

