Files
AgentHub/README.md
T
nntrivi2001 fd7f57929c fix(lanes): auto-detect and report stage for codex lanes (issue 11)
A lane on the 16-node `ship-feature` template showed an untouched pipeline
map and never declared a stage. Four independent causes, fixed at their
shared choke points:

1. Codex emits no hooks, so `touchLaneFromHook` — the only caller of
   `stage-detect.detect()`, and correctly pinned to `claude` — could never
   see a `codex` lane. New `server/lib/codex-ingest.js` tails Codex's own
   rollout journals (`$CODEX_HOME/sessions/**/rollout-*.jsonl`), maps its
   tool calls onto the hook-shaped `{tool_name, tool_input}` the matcher
   already understands, and feeds the existing detect path plus an
   idle/running status mirror under the same guard hooks use. Polled every
   `DASHBOARD_CODEX_POLL_MS` ms (default 15000, `0` disables).
2. `resolveLaneArg` matched on cwd alone, so with a claude and a codex lane
   at one directory (the composite `idx_lanes_cwd_tool`) a `ccam stage`
   call could drive the sibling lane's card. It now resolves by
   `--lane` → `CCAM_LANE_ID` → `--tool`/harness marker → owning lane, and
   errors naming both candidates rather than picking one. Every pane the
   dashboard starts carries `CCAM_LANE_ID` (`tmux new-session -e`, or an
   assignment prefix when typing into a live shell pane).
3. `recordDetection`'s declared-wins veto had no exit: a lane left at the
   pipeline's last node rejected every detection for the rest of its life.
   A declaration on the LAST node no longer vetoes — the pipeline is over
   there, so a later detection is new work. Nothing else is relaxed, and
   detection still never writes `stage` nor renders `done`.
4. The run console pre-filled the pipeline's Claude slash command into
   codex panes, which Codex reads as prose — so the skill that would have
   called `ccam stage` never ran. Claude lanes only now.

A codex lane's *declared* stage still depends on the session calling
`ccam stage`; `AGENTS.md` is the only lever CCAM has, so it now says so.

Verified: server 1345/1345, client 441/441, and end-to-end against a real
codex lane row (detected_stage/detected_signal set, declared stage
untouched, no node rendered done).
2026-08-27 16:38:23 +07:00

7.1 KiB

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.

(Tiếng Việt: README.vi.md)

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 stage still shows progress — rendered dashed amber and never as done, because an inference is not evidence.
  • Run Claude Code or Codex from the browser. A real terminal (tmux + a real PTY, rendered with xterm.js) attached to a lane's directory — the exact TUI you'd see locally, fully interactive, resumable, and attachable from a real terminal too via ccam lanes shell. Which CLI it starts is the lane's own setting.
  • Analytics (in-dashboard tab), alerts, 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 as a Claude Code plugin

Two commands on a machine that has nothing but Claude Code, no clone and no npm run setup:

/plugin marketplace add Smartgift-AI/Claude-Code-Monitor
/plugin install ccam@claude-code-agent-monitor-plugins

The first session start installs the hooks, boots the server, puts ccam on PATH and connects the MCP tools; it runs detached, so the session never waits on it. /ccam-doctor reports the state, /ccam-open builds the UI and prints the URL, /ccam-update refreshes after a plugin update. This path needs Node >= 22.5 (no native better-sqlite3, so the server uses node:sqlite). Details, including what to delete on uninstall: docs/PLUGINS.md.

Install from a checkout

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, DASHBOARD_CLIENT_DIST overrides where the built UI is served from (defaults to client/dist; the plugin install points it at its own runtime directory). postinstall writes the Claude Code hook entries that feed the dashboard — do not run it when the ccam plugin is installed, or every event is counted twice.

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 lanes pipeline    # this lane's pipeline template, or switch it
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

Every lane also carries the CLI that drives it — claude (the default) or codex — picked in the Add-lane modal and fixed for the life of the lane. A Codex lane's run panel offers Codex's own sandbox, approval and reasoning-effort choices, and resumes with codex resume --last. Codex emits none of this dashboard's hooks, so a Codex lane records its runs but stays absent from Sessions and Analytics. Its stage detection works anyway, from Codex's own rollout journals on disk ($CODEX_HOME/sessions/**/rollout-*.jsonl, polled every DASHBOARD_CODEX_POLL_MS ms, default 15000) — what a Codex lane cannot get for free is a declared stage, since only the session itself can call ccam stage.

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's pipeline template can be switched after creation — ccam lanes pipeline <template> from the terminal, or the pipeline-template picker next to the lane's title in the Workspace detail panel. Both re-resolve the lane's current declared stage against the new template's nodes and warn if it no longer matches one.

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
docs/ Architecture, API, lanes, database
plugins/ Claude Code plugins shipped with the dashboard

Docs