Architecture
┌──────────────────┐ hook events (HTTP POST)
│ claude session A │ ────────────────────────┐
└──────────────────┘ │
▼
┌──────────────────┐ ┌──────────────────────┐
│ claude session B │ ──────────────▶│ ccmon-daemon │
└──────────────────┘ │ (Python, asyncio) │
│ │
┌──────────────────┐ blocks 540s │ - per-session state │
│ ccmon-approve │ ──────────────▶│ - SQLite event log │
│ (per perm req) │ ◀──── decision │ - HTTP API on 7777 │
└──────────────────┘ │ - notify-send │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ CLI client / UI │
└──────────────────────┘
Pieces
ccmon-daemon— long-running Python (asyncio) process. Maintains authoritative session state in memory; mirrors to SQLite for crash recovery; serves an HTTP API on127.0.0.1:<port>and a WebSocket for the UI.ccmon-event— a tiny non-blocking hook script. Claude Code invokes it forSessionStart,PreToolUse,PostToolUse,Notification,Stop, etc. It POSTs the payload to/eventand exits in milliseconds.ccmon-approve— the blocking hook forPermissionRequest. It POSTs to/approvaland waits up to 540 s for a decision, then returnsallow/deny/askto Claude Code.ccmon— the CLI client.ccmon-ui— the Tauri desktop app. Connects only over the WebSocket.
Identification
Sessions are identified by Claude Code's own session_id (a UUID
present in every hook payload). The daemon never spawns claude
— it only observes.
State
- In memory — the authoritative copy of every session, every pending approval, and the recent event window.
- SQLite at
~/.local/state/ccmon/ccmon.db— every event, every approval decision, every rule definition, persisted for crash recovery and history. - Filesystem
portfile —~/.local/state/ccmon/portcarries the daemon's bound port for the CLI and UI.
Fail-safe
If the daemon is down:
ccmon-eventexits 0 silently — Claude Code keeps running.ccmon-approveexits 0 silently — Claude Code shows its own in-terminal permission prompt.
You never lose a session because the daemon crashed; the worst case is that the desktop UI is bypassed for the duration.
Concurrency caps
Two ceilings to be aware of:
claude -psummarizers — capped at 2 concurrent to avoid hitting your rate limits. Summaries cache for 30 s.- Approval block —
ccmon-approveblocks at most 540 s before falling back toask. Claude Code's hook timeout is 600 s; we cut off slightly under so the fallback is clean.
Recursion guard
When the daemon spawns claude -p for a summary, it sets
CCMON_SUMMARIZER=1 in the child's environment. Hook scripts no-op
when they see it — that's how summaries don't show up as new sessions
inside ccmon.