The two `upLane qc option` tests booted a real stack and then only released the slot. `downLane` locates a service's pid file through the lane's slot directory, so releasing the slot first orphaned the child with nothing left able to reach it — one `python3 -m http.server` survived every run, holding a port from a pool that is only ten wide. Thirteen had accumulated; the eleventh run onwards fails with EPORTBUSY in whichever test boots next, which reads as an unrelated flake. Both tests now stop the stack before releasing the slot, and assert the port went quiet — so a teardown that breaks again fails here rather than leaking into the next run. A suite-level `after` covers the case a test throws before its own teardown; it runs before SUITE_ROOT is removed, since the pid files it needs live inside it.
Claude Code Monitor
Internal SmartGift build. Local-first dashboard for Claude Code: hooks POST every tool call to an Express + SQLite server, a React UI updates over WebSocket, and lanes track parallel agent work through a pipeline.
Internal build — all rights reserved.
What it does
- Sessions, agents, events. Everything Claude Code emits, recorded and searchable: tool calls, token usage, cost, subagent trees, transcripts.
- Lanes. One lane per working directory, surviving session restarts. A lane moves through pipeline stages and the dashboard shows where it is.
- Stage detection. The stage is inferred from the tool stream, so a session
that never calls
ccam stagestill shows progress — rendered dashed amber and never as done, because an inference is not evidence. - Run Claude from the browser. Spawn a session in a lane's directory, stream its output, send follow-ups, resume any past session.
- Analytics, alerts, Kanban and a workflow view, plus an MCP server and a CLI.
Requirements
Node >= 20 (engines in package.json). Node 24 is what the test suites
are verified on — node 25 currently breaks 6 server tests through a
better-sqlite3 ABI mismatch and 20 client tests through a global localStorage
change.
Install and run
npm run setup # root, client and vscode-extension dependencies
npm run build # builds the client into client/dist
npm start # serves the built client and the API on :4820
Open http://localhost:4820.
Development, with hot reload:
npm run dev # server on :4820, Vite client on :5173
DASHBOARD_PORT overrides the port. postinstall writes the Claude Code hook
entries that feed the dashboard.
The CLI
ccam is linked by npm run setup; otherwise call node bin/ccam.js.
ccam status # is the dashboard up
ccam start # start it in the background and wait for healthy
ccam sessions # recent sessions
ccam lanes # lanes with stage and progress
ccam stage <name> # declare the current lane's stage
ccam tail # live event feed
ccam --help lists the rest.
Lanes
A lane is a working directory the dashboard watches. Two kinds:
- adopted — a directory you already had. The dashboard only reads it; it is never reset or deleted.
- managed — a git worktree the dashboard created under
LANES_ROOT. It owns the full lifecycle and may reset or remove it, behind a three-check destroy guard and a counted preflight the caller has to echo back.
ccam lanes add --cwd /path/to/repo --title "My feature" # adopt
ccam lanes add --repo /path/to/repo --slug my-feature # managed worktree
The declared stage comes from ccam stage. The inferred stage comes from tool
events and expires after DETECTION_TTL_MS (default 5 minutes), so a lane can
move backwards between work sessions. Detection never writes the declared stage,
and an inferred node never renders as done.
A lane can also run its own application stack, isolated per lane, when its
repository declares a profile at <repo>/.ccam/profile/ — a profile.env of
declarations plus shell hooks the dashboard calls. Each lane gets a slot, and its
ports and per-lane directories derive from it:
ccam lanes up # boot the stack of the lane owning this directory
ccam lanes runtime # slot, ports, service health
ccam lanes logs api # tail a service log
ccam lanes down
Services are fully detached, so restarting the dashboard never stops a running lane. This is resource namespacing on the host, not a container: lanes run as the same user and share the network.
docs/LANES.md has the pipeline model, the destroy guard, the
preflight contract, the Workspace page, GET /api/lanes/:id/git, and the full
runtime/profile contract.
Tests
npm run test:server # node:test
npm run test:client # Vitest
Both must be green before a commit; the pre-commit hook runs them plus Prettier.
Layout
| Path | What |
|---|---|
server/ |
Express API, SQLite schema, hook ingest, lane and worktree libraries |
client/ |
React 18 + Vite + Tailwind dashboard |
bin/ccam.js |
CLI |
mcp/ |
MCP server exposing read-only dashboard tools |
desktop/ |
Electron wrapper that embeds the server |
docs/ |
Architecture, API, lanes, database, deployment |
plugins/ |
Claude Code plugins shipped with the dashboard |
Docs
ARCHITECTURE.md— request flow, schema, WebSocket surfacedocs/LANES.md— lanes, pipelines, stage detectiondocs/API.md— REST endpoints (openapi.yamlis generated)docs/DATABASE.md— tables and migrationsINSTALL.md·DEPLOYMENT.md·DESKTOP.mdCLAUDE.md— the rules an agent working in this repo must follow