The READMEs never said tmux was needed, so a fresh install only found out when Start Run failed with nothing explaining why. Both doctors were silent about it too. - README/README.vi: a prerequisites table (node, npm, git, tmux, claude, codex, gh, systemd) and per-OS tmux install + verify steps; the feature list is regrouped into observe/drive/configure and now covers analytics, alerts, import, remote sources, the config explorer and Update all. - INSTALL: Step 0 for the system packages, plus a troubleshooting entry separating "tmux missing" from "the server has a different PATH". - SETUP/docs/README: the same troubleshooting entry and a pointer to Step 0. - plugin-doctor: a tmux row, WARN and never FAIL - the run console and `lanes shell` break without it, nothing else does. Reuses tmux.js's own isTmuxAvailable(). - agenthub doctor: asks GET /api/run/tmux instead, because the PATH that decides whether a run starts is the server's, not the caller's shell's.
19 KiB
Setup Guide
A comprehensive guide to setting up and configuring the Agent Dashboard, including how it integrates with Claude Code, environment variables, container deployment, and troubleshooting common issues.
How it works
Agent Dashboard integrates with Claude Code through its native hook system. When Claude Code performs any action (session start, tool use, turn completion, subagent finish, session exit), 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.
Claude Code → hook fires → hook-handler.js → POST /api/hooks/event
↓
Browser ← WebSocket broadcast ← Express server ← SQLite
No extra Claude Code configuration is required in the normal host-run path — when you start the dashboard with npm run dev or npm start, the server configures the hooks automatically on startup. Container deployments are the exception: after the container is up, run npm run install-hooks on the host so Claude Code points at http://localhost:4820.
Configuration
Hook auto-installation
When the dashboard is running directly on the host, the server writes the following to ~/.claude/settings.json every time it starts:
{
"hooks": {
"SessionStart": [{ "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" SessionStart" }] }],
"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" }] }],
"SessionEnd": [{ "hooks": [{ "type": "command", "command": "node \"/path/to/scripts/hook-handler.js\" SessionEnd" }] }]
}
}
Note
Note:
SessionStartandSessionEndhooks do not support thematcherfield — they fire unconditionally on every session start and exit.
Existing hooks in that file are preserved. The dashboard only adds or updates entries that contain hook-handler.js.
To re-run hook installation manually:
npm run install-hooks
Tip
Container note: if you run the dashboard inside a container (a Dev Container, Codespaces, or your own image), do not install hooks from in there — the hook path a container writes points at the container filesystem, not the host. Run
npm run install-hookson the host instead. As a safeguard (issue #193) the installer detects container execution and refuses to run (exiting non-zero) so it can never poison a bind-mounted host~/.claude. If Claude Code itself genuinely runs inside that container, override withAGENTHUB_ALLOW_CONTAINER_HOOKS=1 npm run install-hooks.
Note
Prefer a ready-made dev environment? This repo ships an optional Dev Container (
.devcontainer/) for VS Code / GitHub Codespaces — Node 22, native build tools forbetter-sqlite3, Python, and ports4820/5173preconfigured. It's purely opt-in and changes nothing for host-based development. See.devcontainer/README.md. (Hooks remain host-side there too.)
Environment variables
| Variable | Default | Description |
|---|---|---|
DASHBOARD_PORT |
4820 |
Port the Express server listens on |
CLAUDE_DASHBOARD_PORT |
4820 |
Port the hook handler uses when posting events to the dashboard |
DASHBOARD_DB_PATH |
data/dashboard.db |
Path to the SQLite database file |
NODE_ENV |
development |
Set to production to serve built client |
DASHBOARD_CLIENT_DIST |
client/dist |
Directory the built UI is served from in production. Set by the agenthub plugin bootstrap to its own runtime dir, because the plugin cache is replaced on every update |
AGENTHUB_IMPORT_MAX_BYTES |
1073741824 (1 GB) |
Maximum size per uploaded file on /api/import/upload |
AGENTHUB_IMPORT_MAX_FILES |
2000 |
Maximum number of files per upload request |
AGENTHUB_IMPORT_MAX_EXTRACT_BYTES |
4294967296 (4 GB) |
Maximum uncompressed bytes any single archive is allowed to expand to (zip-bomb defense) |
Example with a custom port:
DASHBOARD_PORT=9000 npm run dev
Note
You usually do not need to set
DASHBOARD_PORTmanually.npm run devis wrapped byscripts/dev.js, which probes both127.0.0.1and::1(so an SSHLocalForwardbound to one loopback can't slip past) and picks the first free port in4820–4859automatically. The chosen port is propagated to the Vite dev proxy viaDASHBOARD_PORT, and the Express server writes it to~/.claude/.agenthub.jsonso the Claude Code hook handler discovers it without any env var.Multiple dashboards can run side by side — for example two
npm run devcheckouts, ornpm run devalongsidenpm start. Each one appends its{port, pid, startedAt}entry to the discovery file, andscripts/hook-handler.jsfan-outs every hook event to every live entry, so both UIs keep their real-time stream.Setting
CLAUDE_DASHBOARD_PORT=Noverrides discovery entirely and forces the hook handler to a single port — useful for tests and container setups where the in-process discovery file isn't reachable from the host.If you bypass the picker (e.g.
npm run dev:raw, container builds, or anything else that callsnode server/index.jsdirectly), make sure your client is built / proxied against the port the server actually bound.
Agent extension setup (Claude Code + Codex)
This repository ships extension files for both agent ecosystems:
- Claude Code:
CLAUDE.md.claude/rules/*skills/*(theagenthubplugin's own skills)agents/*(theagenthubplugin's own agents)
- Codex:
AGENTS.md.codex/config.toml.codex/rules/default.rules.codex/agents/*.codex/skills/*
See .codex/README.md for Codex extension details.
PWA configuration (optional)
The dashboard ships as a Progressive Web App. No configuration is required — its manifest and service worker are included out of the box.
Customising the manifest: Edit client/public/manifest.json. Common fields to change:
name/short_name— displayed on the home screen / docktheme_color— address bar / title bar tint (default:#6366f1)background_color— splash screen backgroundstart_url— entry point when launched from home screen
Updating the service worker cache: The SW has a CACHE_NAME constant (e.g. dashboard-v2). After deploying new assets, bump the version string to force browsers to re-fetch — though for the dashboard this is rarely needed: hashed /assets/* URLs are immutable per build, everything else is fetched network-first with cache fallback, and a controllerchange listener in the client reloads the page exactly once when a new SW takes over, so a rebuild propagates without a hard refresh.
Browser support: PWA install prompts appear in Chrome 107+, Edge 107+, and Firefox 110+ (desktop and Android). Safari supports apple-mobile-web-app-capable for iOS home-screen mode but does not show an install banner.
Verifying PWA status: Open DevTools → Application → Manifest to confirm the manifest loads. Check the Service Workers section to verify the SW is registered and active. The Lighthouse PWA audit should pass all core checks.
Database
The SQLite database is created automatically at data/dashboard.db on first run. The directory is created if it does not exist. The database uses WAL mode for concurrent reads and foreign keys for referential integrity.
Clear all data
To remove all sessions, agents, events, and token usage (useful after running seed data or for a clean start):
npm run clear-data
Data management via Settings page
The Settings page (/settings) provides a UI for:
- Model Pricing — view and edit per-model cost rates, reset to defaults, add custom models
- Hook Configuration — check which hooks are installed and reinstall them
- Data Export — download all sessions, agents, events, and pricing as a JSON file
- Session Cleanup — abandon stale active sessions after N hours, purge old completed sessions after N days
- Clear All Data — remove all sessions, agents, events, and token usage
- Data Management and About sections render with loading placeholders while server info is being fetched, so the page is always fully navigable
Seed demo data
To populate the dashboard with sample sessions, agents, and events for UI exploration:
npm run seed
Importing existing Claude Code history
The dashboard automatically imports sessions from ~/.claude/projects/ on
every startup, so if Claude Code has been used on this machine, you'll
see history immediately after the first launch. If you need to bring in
history from another machine, from a backup, or just force a rescan, use
Settings → Import History in the UI — it's a guided, drag-and-drop
experience with live progress.
Pick the right mode
flowchart TD
Q["Where is the history?"] --> Q1{Is it on this machine<br/>under ~/.claude/projects?}
Q1 -->|yes, and I just want<br/>to re-scan| M1["Mode: Rescan default folder<br/>one click"]
Q1 -->|yes, but in another folder<br/>on this machine| M2["Mode: Scan a folder<br/>paste the absolute path"]
Q1 -->|no — it's on another machine<br/>or in an archive file| M3["Mode: Upload files<br/>drag-drop JSONL or archive"]
M3 --> PREP["Archive source:<br/>tar -czf claude-history.tar.gz<br/>-C ~/.claude projects"]
style M1 fill:#10b981,stroke:#34d399,color:#fff
style M2 fill:#f59e0b,stroke:#fbbf24,color:#000
style M3 fill:#a855f7,stroke:#c084fc,color:#fff
Step-by-step: moving history from one machine to another
On the source machine, bundle the projects folder:
# macOS / Linux
tar -czf claude-history.tar.gz -C ~/.claude projects
# Windows (PowerShell, via built-in tar)
tar -czf claude-history.tar.gz -C "$env:USERPROFILE\.claude" projects
Transfer the resulting claude-history.tar.gz to the destination machine
however you like — AirDrop, scp, USB, cloud storage.
On the destination machine, in the dashboard:
- Open Settings → Import History.
- Pick Upload files (the third tab).
- Drag the archive onto the drop zone.
- Click Upload & Import and watch the progress.
- When the green result card appears, open Analytics → Cost to confirm per-model token totals and estimated cost.
Supported inputs
Any of the following can be dropped onto the upload zone or found inside a folder given to Scan a folder:
.jsonl— session transcripts.meta.json— subagent metadata sidecars.zip— extracted with path-traversal protection.tar,.tar.gz,.tgz— extracted via thetarpackage.gz— single gzipped JSONL (streaming-decompressed)
Accuracy guarantees
- Idempotent — re-importing never double-counts. Sessions are deduplicated by UUID.
- Cost-preserving — the
token_usagetable usesbaseline_*columns to preserve pre-compaction token totals, so re-ingesting a compacted transcript never erases historical cost. - Same parser as live —
parseSessionFile+importSessionis the single source of truth for both hook-driven ingestion and manual import, so imported numbers match captured numbers exactly.
Safety
Archive extraction is hardened against path traversal and archive bombs.
The defaults are generous for real-world transcripts but tight enough to
stop obvious attacks; see the env vars table above for
AGENTHUB_IMPORT_MAX_BYTES, AGENTHUB_IMPORT_MAX_FILES, and
AGENTHUB_IMPORT_MAX_EXTRACT_BYTES.
CLI alternative
For scripts and automation, the same logic runs from the terminal:
# Import (or re-import) everything under ~/.claude/projects
npm run import-history
# Dry run — show what would be imported without writing
node scripts/import-history.js --dry-run
# Scope to a single project dir
node scripts/import-history.js --project my-project
Scripts reference
| Script | Command | Description |
|---|---|---|
setup |
npm run setup |
Install all dependencies (server + client) |
dev |
npm run dev |
Start server + client in development mode |
start |
npm start |
Start server in production mode |
build |
npm run build |
Build the React client to client/dist/ |
install-hooks |
npm run install-hooks |
Write Claude Code hooks to ~/.claude/settings.json |
clear-data |
npm run clear-data |
Delete all data from the database |
seed |
npm run seed |
Insert demo sessions/agents/events |
import-history |
npm run import-history |
Import legacy sessions from ~/.claude/ (also runs on startup) |
claude |
Claude CLI | Uses CLAUDE.md, .claude/rules, and .claude/skills automatically |
test |
npm test |
Run all server and client tests |
test:server |
npm run test:server |
Run server integration tests only |
test:client |
npm run test:client |
Run client unit tests only |
format |
npm run format |
Format all files with Prettier |
format:check |
npm run format:check |
Check formatting without writing |
Makefile targets
All npm scripts are mirrored as make targets for convenience. Run make help to list them:
make help
Commonly used targets:
| Make target | Equivalent npm command | Description |
|---|---|---|
make setup |
npm run setup |
Install all dependencies (root + client) |
make dev |
npm run dev |
Start server + client in watch mode |
make build |
npm run build |
Build the React client for production |
make start |
npm start |
Start the production server |
make prod |
npm run build && npm start |
Build then start in one step |
make test |
npm test |
Run all tests (server + client) |
make test-server |
npm run test:server |
Run server tests only |
make test-client |
npm run test:client |
Run client tests only |
make format |
npm run format |
Format all files with Prettier |
make format-check |
npm run format:check |
Check formatting without writing |
make seed |
npm run seed |
Load demo data |
make clear-data |
npm run clear-data |
Delete all data rows |
Troubleshooting
better-sqlite3 errors during npm install / npm run setup
These warnings are harmless. better-sqlite3 is an optional dependency — if it cannot compile, npm skips it and the server falls back to Node.js built-in node:sqlite (available on Node 22+).
You do not need Python, Visual Studio Build Tools, or any C++ compiler to run this project on Node 22+.
If you are on Node 20 or 21 and better-sqlite3 prebuilds are not available for your platform (there is no node:sqlite fallback below Node 22), you have two options:
- Upgrade to Node.js 22+ — the built-in
node:sqlitefallback requires no native compilation at all - Install build tools and run
npm rebuild better-sqlite3:- Windows: install Visual Studio Build Tools with the C++ workload
- macOS:
xcode-select --install - Linux:
sudo apt install python3 make g++(Debian/Ubuntu)
"SQLite backend not available" error on startup
This means neither better-sqlite3 nor node:sqlite could be loaded. The most common cause is running Node.js < 22 without better-sqlite3 prebuilds. Upgrade to Node.js 22+ to resolve this.
Database is locked / busy errors
The SQLite database uses WAL mode with a 5-second busy timeout. If you see lock errors:
- Ensure only one dashboard server instance is running
- Check for zombie
node server/index.jsprocesses:ps aux | grep server/index - Delete
data/dashboard.db-walanddata/dashboard.db-shmif the server was killed uncleanly, then restart
The run console won't start a run / the terminal never attaches
Runs are tmux-backed: the server executes the tmux binary and has no fallback.
tmux -V # missing → sudo apt install tmux
curl -s http://localhost:4820/api/run/tmux # {"available":true}
curl -s "http://localhost:4820/api/run/binary?tool=claude" # {"found":true,"path":"…"}
{"available":false} while your own shell finds tmux means the server's
PATH differs from yours — typically a systemd --user service, which never
reads your shell profile. Install tmux system-wide (/usr/bin/tmux) and
re-run agenthub service uninstall && agenthub service install. The same
applies to claude / codex: a run launches them by name.
Install instructions per OS: INSTALL.md → Step 0.
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 a hooks section with entries referencing hook-handler.js. If not, run:
npm run install-hooks
Check 3 — Did you start a new Claude Code session after the server started?
Hooks only apply to sessions started after installation. Restart Claude Code.
Check 4 — Is Node.js in PATH when Claude Code runs hooks?
On some systems, the shell environment when Claude Code fires hooks may not include the full PATH. Test with:
node --version
If Node.js is not found, use the full path to node in the hook command. Edit scripts/install-hooks.js, replace node with the absolute path (e.g. /usr/local/bin/node), and re-run npm run install-hooks.
Dashboard shows "Disconnected" in the sidebar
The WebSocket connection to the server failed. Ensure the server is running:
npm run dev
The client will automatically reconnect every 2 seconds once the server is available.
Events Today shows 0 despite recent activity
This was a known timezone bug (fixed in current version). If you are still seeing this, ensure you are running the latest code and restart the server.
Port 4820 already in use
DASHBOARD_PORT=4821 npm run dev
Then update the Vite proxy in client/vite.config.ts:
proxy: {
"/api": "http://localhost:4821",
"/ws": { target: "ws://localhost:4821", ws: true }
}
And make sure Claude Code posts hooks to the new port:
CLAUDE_DASHBOARD_PORT=4821 claude
# or edit scripts/hook-handler.js and change the default port