Claude Code Agent Monitor
A professional monitoring platform for Claude Code agent activity. Captures sessions, agents, and tool events via native hooks, persists them in SQLite, and streams updates to a React UI over WebSocket — with no external services required.
System Overview
Claude Code Agent Monitor integrates with Claude Code through its native hook system. When Claude Code performs any action — tool use, session start, subagent orchestration, session end — it fires a hook that calls a small Node.js script bundled with this project. That script forwards the event over HTTP to the dashboard server, which stores it in SQLite and broadcasts it to the browser over WebSocket.
End-to-end data pipeline from Claude Code to the browser
The server binds to 0.0.0.0 but everything runs on your machine. No
data leaves your system. No API keys. No external services.
What's Included
Every feature is driven by real hook events — nothing is hardcoded or simulated in production mode.
Live Dashboard
Overview stats, active agent cards with collapsible subagent hierarchy, and recent activity feed — all updating in real-time via WebSocket.
Kanban Board
5-column agent status board (Idle / Connected / Working / Completed / Error) with paginated columns (10 per page) and horizontal scroll on overflow.
Sessions Table
Searchable, filterable, paginated table of all sessions with status badges, agent counts, and duration.
Session Detail
Per-session agent hierarchy tree (parent/child with expand/collapse) and full chronological event timeline with tool names and summaries.
Activity Feed
Real-time streaming event log with pause/resume, pagination, and buffering while paused.
Analytics
Token usage, tool frequency, agent type distribution, activity heatmap, session trends, and donut charts.
WebSocket Push
Sub-5ms update latency. Automatic 2-second reconnection. Heartbeat ping/pong dead-connection detection.
Statusline
Standalone CLI statusline showing model, user, git branch, context window bar, and token counts.
History Import
Automatically imports sessions from ~/.claude/ on server startup
with correct historical timestamps. Recently-modified transcripts are imported
as active sessions so the dashboard reflects real state immediately.
Transcript Cache
Stat-based incremental JSONL reader shared across hook handler and compaction scanner. Avoids duplicate I/O, bounds memory with automatic eviction on session end, and enables smarter session reactivation from transcript activity.
Subagent Hierarchy
Collapsible parent-child agent tree on Dashboard and Session Detail. Agents with subagents show expand/collapse chevrons; leaf agents show a dot indicator. Auto-expands when subagents are active. Correctly tracks backgrounded subagents without premature completion.
Cost Tracking
Per-model cost estimation with configurable pricing rules. View total and per-session cost breakdowns based on input, output, and cache token rates. Compaction-aware token accounting preserves totals across context compressions so no usage is ever lost.
Settings & Management
Model pricing editor, hook configuration status, JSON data export, session cleanup (abandon stale, purge old), and system information dashboard.
Local MCP Server
Enterprise-grade local MCP sidecar under mcp/ with typed tools,
transport-safe runtime, retry-aware API access, and policy gates for mutation and
destructive operations.
Claude + Codex Extensions
Comprehensive instruction, skills, rules, and custom-agent layers for both Claude Code and Codex, plus helper automation to sync runtime extension directories.
Workflow Graphs
D3.js-powered agent orchestration DAG showing spawn patterns across sessions, tool execution Sankey diagram mapping tool-to-tool transitions, and a directed agent pipeline graph revealing which agent types run after which with frequency labels.
Workflow Analytics
Subagent effectiveness scorecards with success rings and sparklines, auto-detected workflow patterns, model delegation flow, error propagation by hierarchy depth, agent concurrency swim-lanes, and session complexity bubble charts.
Session Drill-In
Searchable session selector with pagination to explore any session's agent tree, tool call timeline, and event sequence. Cross-filtering from DAG nodes, compaction impact analysis, JSON export, and real-time WebSocket auto-refresh.
Hook Events Captured
| Hook Type | Trigger | Dashboard Action |
|---|---|---|
PreToolUse |
Agent begins using a tool |
Agent →
Working, current_tool set. If tool is Agent, subagent record
created.
|
PostToolUse |
Tool execution completes |
current_tool cleared. Agent stays
Working (no status change).
|
Stop |
Session/turn ended | Main agent → Idle if subagents still running, else Completed. Running subagents are preserved. Token usage recorded. |
SubagentStop |
Background agent finished | Matched subagent → Completed. Auto-completes session when the last subagent finishes. |
Notification |
Agent sends notification | Event logged to activity feed. Compaction-related notifications tagged as Compaction events. |
Compaction |
/compact detected in JSONL |
Creates a compaction subagent →
Completed. Detected via isCompactSummary entries in the transcript. Token baselines preserve pre-compaction totals. Periodic scanner (every 2 min) catches compactions when no hooks fire.
|
Quick Start
Clone
Clone the repository to your machine
Install
Run npm run setup to install all dependencies
Start
Run npm run dev — server + client launch automatically
Use Claude
Start a new Claude Code session — events appear in real-time
# 1. Clone
git clone https://github.com/hoangsonww/Claude-Code-Agent-Monitor.git
cd Claude-Code-Agent-Monitor
# 2. Install all dependencies (server + client)
npm run setup
# 3. Start in development mode
npm run dev
# → Express server on http://localhost:4820
# → Vite dev server on http://localhost:5173
# 4. (Optional) Build and run the local MCP server
npm run mcp:install
npm run mcp:build
npm run mcp:start
# 5. (Optional) Sync Codex runtime extension folders
npm run codex:sync
# 6. Open the dashboard
# http://localhost:5173 (dev)
# http://localhost:4820 (prod after npm run build && npm start)
Alternative: Docker / Podman
A multi-stage Dockerfile and docker-compose.yml are included.
You can run the monitor with either Docker or Podman and keep the SQLite database in a
named volume.
# Docker Compose
docker compose up -d --build
# Podman Compose
CLAUDE_HOME="$HOME/.claude" podman compose up -d --build
# Plain Docker / Podman
docker build -t agent-monitor .
docker run -d --name agent-monitor \
-p 4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor
When you run the server directly on the host with npm run dev or
npm start, it automatically writes Claude Code hook entries to
~/.claude/settings.json. If you run the dashboard in Docker or Podman,
install hooks from the host with npm run install-hooks after the
container is up, then restart Claude Code.
Optional: Enable MCP and Agent Extensions
This repository also ships a local MCP server under mcp/ and extension
scaffolding for both Claude Code and Codex. These are optional for the dashboard UI, but
recommended for complete local-agent workflows.
# MCP lifecycle
npm run mcp:install
npm run mcp:build
npm run mcp:start
# Sync codex extension templates into runtime dirs
# (copies codex/agents -> .codex/agents and codex/skills -> .agents/skills)
npm run codex:sync
Verification
After starting a Claude Code session, you should see:
| Page | Expected |
|---|---|
| Sessions | Your session listed with status Active |
| Kanban Board | A Main Agent card in the Connected column |
| Activity Feed | Events streaming in as Claude Code uses tools |
| Dashboard | Stats updating in real-time |
Hooks only fire to a running server. If Claude Code was already running when you started the dashboard, restart the Claude Code session.
Configuration
Environment Variables
| Variable | Default | Description |
|---|---|---|
DASHBOARD_PORT |
4820 |
Port the Express server listens on |
CLAUDE_DASHBOARD_PORT |
4820 |
Port used by the hook handler to reach the server (for custom port setups) |
MCP_DASHBOARD_BASE_URL |
http://127.0.0.1:4820 |
Base URL used by the local MCP server when calling dashboard APIs |
DASHBOARD_DB_PATH |
data/dashboard.db |
Path to the SQLite database file |
NODE_ENV |
development |
Set to production to serve built client from
client/dist/
|
Hook Configuration
The server writes the following to ~/.claude/settings.json on every
startup:
{
"hooks": {
"PreToolUse": [
{ "matcher": "*", "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" PreToolUse" }] }
],
"PostToolUse": [
{ "matcher": "*", "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" PostToolUse" }] }
],
"Stop": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" Stop" }] }],
"SubagentStop": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" SubagentStop" }] }],
"Notification": [{ "matcher": "*", "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" Notification" }] }]
}
}
Existing hooks are preserved. The installer only adds or updates entries containing
hook-handler.js.
Scripts Reference
| Script | Command | Description |
|---|---|---|
setup |
npm run setup |
Install all dependencies (server + client) |
dev |
npm run dev |
Start server + client in development mode with hot reload |
dev:server |
npm run dev:server |
Start only the Express server with --watch |
dev:client |
npm run dev:client |
Start only the Vite dev server |
build |
npm run build |
TypeScript check + Vite production build to client/dist/ |
start |
npm start |
Start Express in production mode serving built client |
install-hooks |
npm run install-hooks |
Manually write Claude Code hooks to ~/.claude/settings.json |
seed |
npm run seed |
Insert demo sessions, agents, and events (8 sessions / 23 agents / 106 events) |
import-history |
npm run import-history |
Import historical Claude Code sessions from ~/.claude |
clear-data |
npm run clear-data |
Delete all data from the database (keeps schema) |
test |
npm test |
Run all server and client tests |
test:server |
npm run test:server |
Server integration tests only (Node built-in test runner) |
test:client |
npm run test:client |
Client unit tests only (Vitest + Testing Library) |
mcp:install |
npm run mcp:install |
Install dependencies for the local MCP package under mcp/ |
mcp:typecheck |
npm run mcp:typecheck |
Type-check MCP source without emitting build output |
mcp:build |
npm run mcp:build |
Compile MCP server into mcp/build/ |
mcp:start |
npm run mcp:start |
Start compiled MCP server over stdio transport |
mcp:dev |
npm run mcp:dev |
Run MCP server in dev mode with tsx |
mcp:docker:build |
npm run mcp:docker:build |
Build MCP container image with Docker (agent-dashboard-mcp:local) |
mcp:podman:build |
npm run mcp:podman:build |
Build MCP container image with Podman
(localhost/agent-dashboard-mcp:local)
|
codex:sync |
npm run codex:sync |
Sync codex/agents + codex/skills into runtime
directories (.codex/agents + .agents/skills)
|
format |
npm run format |
Format all files with Prettier |
format:check |
npm run format:check |
Check formatting without writing |
MCP & Agent Extensions
In addition to dashboard telemetry, this project now includes a production-grade local MCP server and complete extension scaffolding for both Claude Code and Codex. This gives agents a richer local tool surface while keeping all execution local-first.
Local extension architecture: host instructions + skills + local MCP sidecar
Local MCP Server Runtime
The mcp/ package exposes dashboard-oriented tools for AI agents. Mutation and
destructive operations are policy-gated by environment variables and disabled by default.
| Component | Location | Notes |
|---|---|---|
| MCP source | mcp/src/ |
TypeScript server, tools, policy guards, transport bootstrap |
| MCP build output | mcp/build/ |
Compiled JavaScript used by local stdio server runtime |
| MCP docs | mcp/README.md |
Tool catalog, architecture diagrams, host integration examples |
| Runtime commands | npm run mcp:install|typecheck|build|start|dev|docker:build|podman:build |
Install, validate, compile, and run MCP locally |
Agent Extension Layout
| Target | Files | Purpose |
|---|---|---|
| Claude Code | CLAUDE.md, .claude/rules/ |
Persistent project instructions + path-scoped coding rules |
| Claude Code Skills | .claude/skills/ |
Reusable workflows (onboarding, shipping, MCP ops, live debugging) |
| Claude Code Subagents | .claude/agents/ |
Specialized reviewers for backend, frontend, and MCP code paths |
| Codex Base Instructions | AGENTS.md, codex/rules/default.rules |
Project-wide guidance + execution policy defaults |
| Codex Skills | codex/skills/ |
Task-specific skills aligned to this repository |
| Codex Agents | codex/agents/ |
Reusable custom-agent templates for implementation and review |
| Codex runtime sync | scripts/setup-codex-extensions.js |
Copies templates into .agents/skills and .codex/agents |
Root Helper Scripts
| Script | Role |
|---|---|
scripts/hook-handler.js |
Receives Claude hook payloads over stdin and forwards them to dashboard API |
scripts/install-hooks.js |
Writes/updates hook registration in ~/.claude/settings.json |
scripts/import-history.js |
Imports historical session data into local database |
scripts/seed.js |
Loads deterministic demo data for testing and demos |
scripts/clear-data.js |
Removes persisted rows while preserving schema |
scripts/setup-codex-extensions.js |
Syncs Codex extension templates to active runtime directories |
If hidden directories are restricted in your environment, run
npm run codex:sync with CODEX_PROJECT_AGENTS_DIR and
CODEX_PROJECT_SKILLS_DIR overrides.
System Architecture
Core dashboard telemetry is composed of three processes (Claude hook source, dashboard
server, browser UI). When the local MCP sidecar is enabled, a fourth process
(mcp/) integrates with the same dashboard API.
Full system architecture — Claude Code process → Hook Layer → Server → Browser
Agent State Machine
Agent status transitions driven by hook events
Session State Machine
Session status lifecycle
Data Flow
Event Ingestion Pipeline
Parses JSON and adds hook_type HH->>API: POST {"hook_type":"PreToolUse","data":{...}} API->>TX: BEGIN TRANSACTION TX->>TX: ensureSession(session_id) Note over TX: Creates session + main agent
on first contact TX->>TX: process by hook_type Note over TX: PreToolUse -> agent working
PostToolUse -> clear current_tool
Stop -> idle if subagents, else completed
SubagentStop -> complete matched subagent TX->>TX: insertEvent(...) TX->>TX: COMMIT API->>WS: broadcast("agent_updated", agent) API->>WS: broadcast("new_event", event) WS->>UI: {"type":"agent_updated","data":{...}} UI->>UI: eventBus.publish -> page re-renders
Complete event ingestion from hook fire to browser re-render
Client Data Loading Pattern
Initial load + WebSocket subscription lifecycle
Server Architecture
Express app + HTTP server"] DB["server/db.js
SQLite + prepared statements
better-sqlite3 / node:sqlite fallback"] WS["server/websocket.js
WS server + heartbeat"] HOOKS["routes/hooks.js
Hook event processing"] SESSIONS["routes/sessions.js
Session CRUD"] AGENTS["routes/agents.js
Agent CRUD"] EVENTS["routes/events.js
Event listing"] STATS["routes/stats.js
Aggregate queries"] ANALYTICS["routes/analytics.js
Analytics metrics"] PRICING["routes/pricing.js
Pricing CRUD + cost calc"] SETTINGS_R["routes/settings.js
System info + data mgmt"] WORKFLOWS_R["routes/workflows.js
Workflow visualizations"] INDEX --> DB INDEX --> WS INDEX --> HOOKS INDEX --> SESSIONS INDEX --> AGENTS INDEX --> EVENTS INDEX --> STATS INDEX --> ANALYTICS INDEX --> PRICING INDEX --> SETTINGS_R INDEX --> WORKFLOWS_R HOOKS --> DB HOOKS --> WS SESSIONS --> DB SESSIONS --> WS AGENTS --> DB AGENTS --> WS EVENTS --> DB STATS --> DB ANALYTICS --> DB PRICING --> DB SETTINGS_R --> DB WORKFLOWS_R --> DB
Server module dependency graph
Server Modules
| Module | Responsibility |
|---|---|
server/index.js |
Express app setup, middleware (CORS, JSON 1MB limit), route mounting, static serving in production, HTTP server, auto-hook installation on startup |
server/db.js |
SQLite connection, WAL/FK pragmas, schema migrations (CREATE TABLE IF NOT EXISTS), all prepared statements as a reusable stmts object. Tries
better-sqlite3 first, falls back to built-in
node:sqlite via compat-sqlite.js
|
server/compat-sqlite.js |
Compatibility wrapper giving Node.js built-in node:sqlite
(DatabaseSync) the same API as better-sqlite3 —
pragma, transaction, prepare. Used as automatic fallback on Node 22+
|
server/websocket.js |
WebSocket server on /ws path, 30s ping/pong heartbeat, typed
broadcast(type, data) function
|
routes/hooks.js |
Core event processing inside SQLite transactions. Auto-creates sessions/agents. Switch-case dispatch by hook type. Extracts token usage from Stop events. |
routes/sessions.js |
CRUD with pagination. GET includes agent count via LEFT JOIN. POST is idempotent on session ID. |
routes/agents.js |
CRUD with status/session_id filtering. PATCH broadcasts
agent_updated.
|
routes/events.js |
Read-only event listing with session_id filter and pagination. |
routes/stats.js |
Single aggregate query — total/active counts, status distributions, WS connection count. |
routes/analytics.js |
Extended analytics — token totals, tool usage counts, daily event/session trends, agent type distribution, event type breakdown, average events per session. |
routes/pricing.js |
Model pricing CRUD (list, upsert, delete). Per-session and global cost calculation with pattern-based model matching and specificity sorting. |
routes/settings.js |
System info (DB size, row counts, hook status, server uptime). Data export as JSON. Session cleanup (abandon stale active sessions, purge old completed sessions). Clear all data. Reset pricing to defaults. Reinstall hooks. |
routes/workflows.js |
Aggregate workflow visualization data (agent orchestration, tool transitions, collaboration networks, workflow patterns, model delegation, error propagation, concurrency, session complexity, compaction impact). Per-session drill-in with agent tree, tool timeline, and events. |
Client Architecture
React component tree
Client Routes
Key Client Modules
| Module | Purpose |
|---|---|
lib/api.ts |
Typed fetch wrapper — one method per REST endpoint. All return typed promises. |
lib/types.ts |
TypeScript interfaces: Session, Agent,
DashboardEvent, Stats, Analytics,
WSMessage, plus all workflow-related types
(WorkflowData, SessionDrillIn, etc). Status config
maps.
|
lib/eventBus.ts |
Set-based pub/sub. subscribe(fn) returns an unsubscribe function
for clean useEffect teardown.
|
lib/format.ts |
Date/time formatting helpers — relative time, duration, ISO display. |
hooks/useWebSocket.ts |
Auto-reconnecting WebSocket React hook. 2-second reconnect interval. Publishes messages to eventBus. |
State Management
The client deliberately avoids Redux / Zustand / Context. Each page owns its data and lifecycle. WebSocket events trigger a reload or append — no complex state merging.
Each page pulls initial data from REST then subscribes to eventBus for live updates
There is no cross-page shared state. Each page fetches and owns exactly the data it displays. This simplifies debugging and avoids stale-closure hazards that are common with global stores in long-running WebSocket apps.
Database Design
Entity Relationship Diagram — SQLite schema
Indexes
| Index | Table | Column(s) | Purpose |
|---|---|---|---|
idx_agents_session |
agents | session_id |
Fast agent lookup by session |
idx_agents_status |
agents | status |
Kanban column queries |
idx_events_session |
events | session_id |
Session detail event list |
idx_events_type |
events | event_type |
Filter events by type |
idx_events_created |
events | created_at DESC |
Activity feed ordering |
idx_sessions_status |
sessions | status |
Status filter on sessions page |
idx_sessions_started |
sessions | started_at DESC |
Default sort order |
SQLite Configuration
| Pragma | Value | Rationale |
|---|---|---|
journal_mode |
WAL |
Concurrent reads during writes. Far better for read-heavy dashboards. |
foreign_keys |
ON |
Referential integrity — prevents orphaned agents/events. |
busy_timeout |
5000 |
Wait up to 5s for write lock instead of failing immediately under load. |
API Reference
All endpoints return JSON. Errors follow
{ "error": { "code", "message" } }.
Health
{ "status": "ok", "timestamp": "..." }
Sessions
status, limit,
offset
id)
Agents
status, session_id,
limit, offset
Events, Stats, Analytics
session_id, limit,
offset
Hooks Ingestion
Pricing
Settings
Workflows
{
"hook_type": "PreToolUse",
"data": {
"session_id": "abc-123",
"tool_name": "Bash",
"tool_input": { "command": "ls -la" }
}
}
WebSocket Protocol
| Property | Value |
|---|---|
| Path | ws://localhost:4820/ws |
| Protocol | Standard WebSocket (RFC 6455) |
| Heartbeat | Server pings every 30s — clients that don't pong are terminated |
| Reconnect | Client retries every 2 seconds on disconnect |
Message Envelope
// All messages use this shape
{
type: "session_created" | "session_updated" |
"agent_created" | "agent_updated" | "new_event";
data: Session | Agent | DashboardEvent;
timestamp: string; // ISO 8601
}
Client WebSocket auto-reconnect state machine
Hook Integration
Hook Handler Design
scripts/hook-handler.js is a minimal, fail-safe forwarder. It always exits
0 so it can never block Claude Code regardless of server state.
hook-handler.js flow — always exits 0, never blocks Claude Code
Hook Installation Flow
Hook installation is idempotent — safe to run multiple times
Statusline Utility
The statusline/ directory contains a standalone CLI statusline for Claude
Code — completely independent of the web dashboard. It renders a color-coded bar at the
bottom of the Claude Code terminal showing context window usage, token counts, and git
branch.
Sonnet 4.6 | nguyens6 | ~/agent-dashboard/client | main | ████████░░ 79% | 3↑ 2↓ 156586c
| Segment | Source | Color Logic |
|---|---|---|
| Model | data.model.display_name |
Always cyan |
| User | $USERNAME / $USER |
Always green |
| Working Dir | data.workspace.current_dir |
Always yellow, ~ prefix for home |
| Git Branch | git symbolic-ref --short HEAD |
Always magenta, hidden outside git repos |
| Context Bar | data.context_window.used_percentage |
Green < 50%, Yellow 50–79%, Red ≥ 80% |
| Token Counts | data.context_window.current_usage |
Dim — ↑ input, ↓ output, c cache reads
|
Statusline rendering pipeline — invoked on each Claude Code update
Installation
Add this to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "bash \"/absolute/path/to/statusline/statusline-command.sh\""
}
}
The statusline uses only Python 3.6+ stdlib (sys, json,
os, subprocess). It fails silently on empty input or JSON
errors and never blocks Claude Code.
Settings Page
The /settings route provides a comprehensive management interface with
five sections:
Model Pricing
Editable table of per-model pricing rules. Each Claude model variant has its own
explicit pattern (e.g., claude-opus-4-6%). Rates cover input, output,
cache read, and cache write tokens. Reset to defaults or add custom models.
Hook Configuration
Shows per-hook installation status (PreToolUse, PostToolUse, Stop, SubagentStop, Notification). One-click reinstall if hooks are missing or outdated.
Data Management
View database row counts and size. Session cleanup: abandon stale active sessions after N hours, purge old completed sessions after N days. Danger zone: clear all data with confirmation.
Data Export
Download all sessions, agents, events, token usage, and pricing rules as a single JSON file for backup or analysis.
System Info
Server uptime, Node.js version, platform, active WebSocket connections. At-a-glance health monitoring for the dashboard itself.
Each Claude model variant (e.g., Opus 4.6 vs Opus 4.1) has its own explicit pricing pattern because different model versions have different rates. The cost engine uses specificity sorting — longer patterns match before shorter ones.
Deployment Modes
Development vs production deployment topology
| Aspect | Development | Production |
|---|---|---|
| Processes | 2 (Express + Vite) | 1 (Express only) |
| Client URL | http://localhost:5173 |
http://localhost:4820 |
| API proxy | Vite proxies /api + /ws to :4820 |
Same origin, no proxy |
| File watching | node --watch + Vite HMR |
None |
| Source maps | Inline | External files |
Container Runtime (Docker / Podman)
The production image is OCI-compatible and works with both Docker and Podman. The
server listens on 4820, reads legacy Claude history from a read-only mount,
and persists SQLite data under /app/data.
Container image build and runtime mounts
# Docker Compose
docker compose up -d --build
# Podman Compose
CLAUDE_HOME="$HOME/.claude" podman compose up -d --build
# Stop the stack
docker compose down
# or
podman compose down
| Mount | Purpose |
|---|---|
~/.claude:/root/.claude:ro |
Read historical Claude session files for import without modifying them |
agent-monitor-data:/app/data |
Persist the SQLite database across rebuilds and container restarts |
Claude Code fires hooks from the host machine, not from inside the container. After
the container is healthy on http://localhost:4820, run
npm run install-hooks on the host so hook events post back to the
containerized server.
Docker / Podman
A multi-stage Dockerfile and docker-compose.yml are included.
Both Docker and Podman are fully supported — the image is OCI-compliant.
Quick Start
# Docker Compose
docker compose up -d --build
# Podman Compose
CLAUDE_HOME="$HOME/.claude" podman compose up -d --build
Plain Docker / Podman (no Compose)
# Build the image
docker build -t agent-monitor .
# — or —
podman build -t agent-monitor .
# Run the container
docker run -d --name agent-monitor \
-p 4820:4820 \
-v "$HOME/.claude:/root/.claude:ro" \
-v agent-monitor-data:/app/data \
agent-monitor
Volume Mounts
| Mount | Purpose |
|---|---|
~/.claude:/root/.claude:ro |
Read-only access to legacy session history for automatic import on startup |
agent-monitor-data:/app/data |
Persists the SQLite database across container restarts |
Multi-Stage Build
The Dockerfile uses three stages to minimize the final image size:
| Stage | Purpose |
|---|---|
| server-deps | Installs production node_modules on node:22-alpine. better-sqlite3 is optional — if prebuilds are unavailable, the server falls back to built-in node:sqlite |
| client-build | Runs npm ci + vite build to produce optimized static assets |
| runtime | Clean node:22-alpine with only node_modules, server code, and client/dist |
localhost:4820.
Run npm run install-hooks on the host after starting the container.
Performance Characteristics
| Metric | Value | Notes |
|---|---|---|
| Server startup | < 200ms | SQLite opens instantly; schema migration is idempotent |
| Hook latency | < 50ms | Transaction + broadcast, no async I/O beyond SQLite |
| Client JS bundle | 200 KB / 63 KB gzip | CSS: 17 KB / 4 KB gzip |
| WebSocket latency | < 5ms | Local loopback, JSON serialization only |
| SQLite write throughput | ~50,000 inserts/sec | WAL mode on SSD; far exceeds any hook event rate |
| Max events before slowdown | ~1M rows | Pagination prevents full-table scans |
| Server memory | ~30 MB | SQLite in-process, no ORM overhead |
| Client memory | ~15 MB | React + Tailwind, minimal runtime deps |
Security Considerations
| Area | Approach |
|---|---|
| SQL injection | All queries use prepared statements with parameterized values — no string interpolation |
| Request size | Express JSON body parser limited to 1MB |
| Input validation | Required fields checked before DB operations; CHECK constraints on status enums |
| Hook safety |
Hook handler always exits 0; 5s max lifetime; uses 127.0.0.1 not
external hosts
|
| CORS | Enabled for development; in production same-origin (Express serves the client) |
| Authentication | Intentionally none — local dev tool. Restrict via firewall if exposing on LAN. |
| Secrets | No API keys, tokens, or credentials stored or transmitted anywhere |
| Dependency surface | 5 runtime server deps, 6 runtime client deps (includes D3.js for Workflows) — minimal attack surface |
Troubleshooting
No sessions appearing after starting Claude Code
Check 1 — Is the server running?
curl http://localhost:4820/api/health
# Expected: {"status":"ok","timestamp":"..."}
Check 2 — Are hooks installed?
# Open ~/.claude/settings.json and confirm it contains "hook-handler.js"
# If not, re-run:
npm run install-hooks
Check 3 — Start a new Claude Code session
Hooks only apply to sessions started after installation. Restart Claude Code after starting the dashboard.
Check 4 — Is Node.js in PATH?
On some systems the shell environment when Claude Code fires hooks may not include the
full PATH. Test with node --version. If not found, use the absolute path to
node in the hook command.
Common Issues
| Problem | Solution |
|---|---|
better-sqlite3 errors during install |
This is non-fatal — better-sqlite3 is an optional dependency. On
Node 22+ the server automatically falls back to built-in
node:sqlite. On older Node versions, install Python 3 + C++ build
tools, then run npm rebuild better-sqlite3.
|
| Dashboard shows "Disconnected" |
Server is not running. Start it with npm run dev. Client
auto-reconnects every 2s.
|
| Events Today shows 0 | Ensure you are on the latest version (timezone bug was fixed). Restart the server. |
| Port 4820 already in use |
Run DASHBOARD_PORT=4821 npm run dev, update Vite proxy in
client/vite.config.ts, and re-run
npm run install-hooks.
|
| Stale seed data shown | Run npm run clear-data to wipe all rows, then restart. |
| Hooks show validation error about matcher |
Ensure you're on the latest version — the hook format was updated to use
matcher: "*" string (not object).
|
| "SQLite backend not available" on startup |
Neither better-sqlite3 nor node:sqlite could load.
Upgrade to Node.js 22+ (recommended), or install Python 3 + C++ build tools
and run npm rebuild better-sqlite3.
|
| Docker container runs but no sessions appear |
Hooks run on the host, not inside the container. Run
npm run install-hooks on the host after the container starts.
Verify hooks in ~/.claude/settings.json point to
localhost:4820.
|
Technology Choices
| Technology | Why This Over Alternatives |
|---|---|
| SQLite (better-sqlite3 / node:sqlite) |
Zero-config, embedded, no server process. WAL mode gives concurrent reads.
Synchronous API is simpler than async for this use case.
better-sqlite3 is preferred when prebuilds are available; falls back
to Node.js built-in node:sqlite on Node 22+ when the native module
cannot be compiled.
|
| Express |
Battle-tested, minimal, well-understood. Fastify would be overkill; raw
http module would require too much boilerplate for routing.
|
| ws | Fastest, most lightweight WebSocket library for Node. No Socket.IO overhead needed — we only push typed JSON messages one-way. |
| React 18 | Stable, widely known, strong TypeScript support. No Server Components or RSC needed for a client-rendered local SPA. |
| Vite 6 | Fast builds, native ESM, excellent dev experience. Proxy config handles the dev server split cleanly with no ejection. |
| Tailwind CSS | Utility-first approach keeps styles colocated with markup. No CSS module boilerplate. Custom dark theme config for the dark UI. |
| React Router 6 |
Standard routing for React SPAs. Layout routes with
<Outlet> give clean shell composition without prop drilling.
|
| Lucide React | Tree-shakeable icon library — only imports what's used (~20 icons). No heavy icon font. |
| TypeScript Strict |
Catches null/undefined bugs at compile time.
noUncheckedIndexedAccess prevents array bounds issues in analytics
aggregations.
|
| D3.js + d3-sankey | Industry-standard data visualization library. Powers the Workflows page's 11 interactive sections — DAG layouts, Sankey diagrams, force-directed graphs, bubble charts, and swim-lane timelines. No wrapper libraries needed; direct SVG rendering keeps bundle impact minimal. |
| Python (statusline) | Available on virtually all systems. Handles ANSI and JSON natively with stdlib only. No install step required. |