You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+51-8Lines changed: 51 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,9 +8,21 @@ claude-plane is a self-hosted control plane for managing interactive Claude CLI
8
8
9
9
-**`claude-plane-server`** — Control plane. Serves the frontend, manages sessions, orchestrates jobs, accepts inbound gRPC connections from agents. SQLite storage.
10
10
-**`claude-plane-agent`** — Runs on worker machines. Manages Claude CLI processes in PTYs, buffers terminal output, maintains persistent gRPC connection to the server.
11
-
-**`claude-plane-bridge`** — Connects external services (GitHub, Telegram, Slack) to the server via its REST API. Polls for events, triggers jobs, and relays notifications.
11
+
-**`claude-plane-bridge`** — Connects external services (GitHub, Telegram, Slack) to the server via its REST API. Polls for events, triggers jobs, and relays notifications. Auto-configured when running via Docker.
12
12
-**Frontend** — React 19 + TypeScript SPA embedded via `go:embed` into the server binary. Modes: Command Center (dashboard), single-session terminal view, and Multi-View (2-6 sessions in resizable split panes).
13
13
14
+
## Product-First Development (CRITICAL)
15
+
16
+
**Every code change must be validated against the full user workflow it affects.** We have repeatedly introduced bugs by fixing one piece of code in isolation without tracing how it impacts the rest of the system. Before implementing any feature or fix:
17
+
18
+
1.**Trace the full user story.** Walk through the complete user journey that touches the code you're changing — from the UI action, through the API, to the backend, to the agent, and back. Map every component in the chain.
19
+
2.**Check all consumers.** A session status change affects: the session terminal header, the SessionsPage list, the CommandCenter dashboard cards, the MultiView picker, the event stream, and the reaper. If you change how status works, verify ALL of them.
20
+
3.**Think about state transitions.** What happens when a session goes from `created` → `running` → `waiting_for_input` → `running` → `completed`? What about `created` → `running` → `errored`? What about a machine disconnecting mid-session? Trace every transition.
21
+
4.**Test with real Claude CLI.** The CLI has behaviors that are invisible in unit tests: Ink status bar redraws, periodic tips/updates below the prompt, cursor repositioning escape sequences. Assumptions about terminal output patterns have caused multiple critical bugs.
22
+
5.**Frontend views share data but display independently.** When you invalidate a query, use `refetchType: 'all'` to ensure unmounted components (other pages the user navigates to) also get fresh data. Stale caches cause status mismatches between views.
23
+
24
+
**The cost of tracing a full workflow is 10 minutes. The cost of a bug that reaches production is hours of debugging.**
25
+
14
26
## Architecture Principles
15
27
16
28
1.**Agents dial in, server never dials out.** Workers can be behind NATs/firewalls.
@@ -20,6 +32,19 @@ claude-plane is a self-hosted control plane for managing interactive Claude CLI
20
32
21
33
## Build & Run
22
34
35
+
### Docker (recommended — production & development)
36
+
37
+
```bash
38
+
# Start server + bridge (bridge auto-configures on first run)
39
+
docker compose up -d
40
+
41
+
# The server image includes the bridge binary. On first start,
42
+
# docker-entrypoint.sh auto-generates an API key and bridge.toml.
43
+
# No manual bridge configuration needed.
44
+
```
45
+
46
+
### Local Development
47
+
23
48
**Prerequisites:** Go 1.25+, Node.js 22+
24
49
25
50
```bash
@@ -40,6 +65,7 @@ cd web && npm install && npm run build && cd ..
40
65
./claude-plane-server ca issue-server --ca-dir ./ca --out-dir ./server-cert
41
66
./claude-plane-server ca issue-agent --ca-dir ./ca --machine-id "worker-1"
./claude-plane-server create-api-key --name bridge --admin # for manual bridge setup
43
69
44
70
# Agent subcommands
45
71
./claude-plane-agent run --config agent.toml
@@ -67,6 +93,14 @@ When debugging frontend issues in production mode, verify the bundle hash in the
67
93
68
94
**WebSocket attach failure:** When `runSession` in `session/ws.go` fails to attach to the agent, it must publish end markers (`scrollback_end` + `session_ended`) AND close the WebSocket. Do not fall through to the relay loops — the reader loop would repeatedly call `sendToAgent()` against a missing agent.
69
95
96
+
**Idle detection and CLI noise:** The idle detector (`idle_detector.go`) uses silence-based timing, NOT prompt marker matching. Claude CLI renders its UI with Ink (React for terminals), which sends cursor repositioning escape sequences (`\x1b7`, `\x1b8`, `\x1b[<n>;<m>H`, `\x1b[<n>A/B/C/D`) for status bar redraws even when idle. The `isRepositioningNoise()` classifier filters these out — only sequential text with SGR color codes (`\x1b[...m`) counts as real output. Do NOT attempt to detect idle state by matching prompt characters (❯) — they are persistent TUI elements, not line-delimited prompts.
97
+
98
+
**Machine connection debounce:** When an agent registers, there's a brief gap between `Register()` and `CommandStream()` that causes a legitimate disconnect/reconnect cycle. The connection manager uses a 5-second grace period (`disconnectGrace`) before publishing `machine.disconnected`. Never publish disconnect events immediately.
99
+
100
+
**Session status across views:** Session status is displayed in: terminal header, SessionsPage list, CommandCenter cards, MultiView picker, and event stream. All these views use independent TanStack Query caches. When invalidating session queries, always use `refetchType: 'all'` to ensure unmounted views also refresh — otherwise users see stale status when navigating between pages.
101
+
102
+
**Bridge auto-config in Docker:** The server Docker image includes the bridge binary. `docker-entrypoint.sh` auto-generates an API key via `create-api-key` CLI command and writes `bridge.toml` on first start. Do not require manual bridge configuration for Docker deployments.
103
+
70
104
## Testing
71
105
72
106
```bash
@@ -144,6 +178,12 @@ Single proto file: `proto/claudeplane/v1/agent.proto`. Defines `Register()` and
144
178
|`event/`| In-process pub/sub bus with glob-style pattern matching, WebSocket fanout, webhook delivery with retry |
145
179
|`provision/`| Agent provisioning token generation and install script building |
146
180
|`agentdl/`| Multi-platform agent binary download endpoints (embedded via `go:embed`) |
181
+
|`broker/`| Message broker for inter-component communication |
182
+
|`ingest/`| Data ingestion pipeline for agent metrics and token usage |
183
+
|`logging/`| Structured logging infrastructure — slog TeeHandler writing to stderr + SQLite (async batch) + WebSocket broadcast |
Connectors implement a common interface. Each polls an external service, maps events to job triggers, and relays via the server's REST API.
205
+
Connectors implement a common interface. Each polls an external service, maps events to job triggers, and relays via the server's REST API. In Docker deployments, the bridge binary is embedded in the server image and auto-configured by `docker-entrypoint.sh` — no manual setup required.
166
206
167
207
## Agent Architecture (`internal/agent/`)
168
208
169
209
-`session.go` / `session_manager.go` — PTY-backed process management using `creack/pty`, scrollback file storage, output buffering
170
210
-`client.go` — gRPC client with automatic reconnection and backoff
171
211
-`backoff.go` — Exponential backoff logic for reconnection
172
212
-`health.go` — Health check reporting
173
-
-`idle_detector.go` — Session idle tracking
213
+
-`idle_detector.go` — Silence-based idle detection with noise classifier. Uses `isRepositioningNoise()` to filter Ink cursor-positioning escape sequences from real Claude output. Configurable silence timeout, minimum activity bytes threshold.
174
214
-`scrollback.go` — Scrollback buffer persistence
215
+
-`directory.go` — Working directory resolution and validation
|`hooks/`| TanStack Query hooks for data fetching; `useTerminalSession()` for xterm.js + WebSocket (supports optional WebGL toggle); `useEventStream()` for multiplexed event WS with exponential backoff |
|`hooks/`| TanStack Query hooks for data fetching (~29 hooks); `useTerminalSession()` for xterm.js + WebSocket (supports optional WebGL toggle); `useEventStream()` for multiplexed event WS with exponential backoff and `refetchType: 'all'`|
194
237
|`types/`| TypeScript interfaces for all domain entities |
**Notable frontend libraries:**`@xyflow/react` + `@dagrejs/dagre` for DAG visualization, `cron-parser` + `cronstrue` for cron display, `xterm.js` with WebGL addon for terminal rendering, `react-resizable-panels` for multi-view split panes.
Copy file name to clipboardExpand all lines: web/src/views/ConnectorDetailPage.tsx
+5-1Lines changed: 5 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -390,7 +390,11 @@ export function ConnectorDetailPage() {
390
390
<ConfirmDialog
391
391
open={showDeleteConfirm}
392
392
title="Delete Connector"
393
-
message={`Are you sure you want to delete "${connector.name}"? This cannot be undone.`}
393
+
message={
394
+
connector.connector_type==='telegram'
395
+
? `Are you sure you want to delete "${connector.name}"? The linked notification channel and its subscriptions will also be deleted. This cannot be undone.`
396
+
: `Are you sure you want to delete "${connector.name}"? This cannot be undone.`
Copy file name to clipboardExpand all lines: web/src/views/ConnectorsPage.tsx
+5-1Lines changed: 5 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -173,7 +173,11 @@ export function ConnectorsPage() {
173
173
<ConfirmDialog
174
174
open={deletingConnector!==null}
175
175
title="Delete Connector"
176
-
message={`Are you sure you want to delete "${deletingConnector?.name}"? This cannot be undone.`}
176
+
message={
177
+
deletingConnector?.connector_type==='telegram'
178
+
? `Are you sure you want to delete "${deletingConnector?.name}"? The linked notification channel and its subscriptions will also be deleted. This cannot be undone.`
179
+
: `Are you sure you want to delete "${deletingConnector?.name}"? This cannot be undone.`
0 commit comments