Run dozens of Claude Code agents at once. See every session. Miss nothing.
Running more than a couple of Claude Code agents at once turns into alt-tabbing between terminal windows, missing the exact moment one finishes or silently blocks on a prompt, and merging work you never watched happen. Glimmervoid is the shipped fix: one browser dashboard, live terminal output for every session, exact status instead of a guess, and per-agent git worktrees you can review and merge without leaving the page.
I run my daily agent fleet in it. This README was written inside a Glimmervoid session: Glimmervoid is developed inside Glimmervoid.
Glimmervoid ships from this repo, through two channels. It is not an npm package: nothing is published to any registry, and the github: spec below is the only supported install.
My own always-on machines are provisioned by my dotfiles repo's server profile: it clones this repo to ~/Projects/glimmervoid, runs npm ci and npm run build, installs a systemd user service running node dist/server/index.js, and fronts the remote listener with tailscale serve. Updating is re-running its apply script.
Requires npm 12 or newer (npm --version to check). npm 12 refuses git dependencies by default, so the install carries an explicit opt-in flag. On Windows and macOS this is the whole command: node-pty's registry tarball ships a prebuilds/<platform>-<arch>/ binary that loads with no install script at all, its blocked post-install.js only copies conpty.dll for a node-pty option Glimmervoid never passes, and prepare builds dist/ during npm's git preparation regardless of the install-scripts policy.
npm install -g github:johncwaters/glimmervoid --allow-git=rootOn Linux, node-pty has no prebuilds and must compile through node-gyp, which npm 12's default script-skipping prevents. Install normally, then compile node-pty on its own, with the build toolchain from Requirements installed:
npm install -g github:johncwaters/glimmervoid --allow-git=root
npm rebuild -g node-pty --allow-scripts=node-ptyFolding both into one command also works, at the cost of a far broader scripts opt-in:
npm install -g github:johncwaters/glimmervoid --allow-git=root --dangerously-allow-all-scriptsOn an older npm, run the same command through npx npm@12 install -g ... without upgrading. The npm 12 floor is hard, not advisory: npm 11 global installs from git specs are broken outright (npm/cli#9406, the package lands as a link into npm's cache temp clone, which npm then deletes).
Your ~/.npmrc must carry no allow-scripts line while the install runs. Any value for that setting, whether it comes from the command line or from any npmrc, propagates into the project-scoped child install npm uses to prepare a git dependency, and that child refuses it with EALLOWSCRIPTS; take the line out for the install and put it back afterwards. That is also why the targeted --allow-scripts node-pty form cannot simply be added to the install command, and why a package-level allowScripts field in this repo's package.json would not help either: npm 12 reads that field only for project-scoped installs and skips it whenever the run is a global one. All of it verified against npm 12.0.2.
The separate rebuild is a global command, which is why --allow-scripts is legal there, but two nearby spellings look right and are not. Run it from outside the installed package: from inside $(npm root -g)/glimmervoid the same command is project-scoped and dies with EALLOWSCRIPTS. And name node-pty rather than the wrapper: npm rebuild -g glimmervoid --allow-scripts=node-pty exits 0 and reports rebuilt dependencies while rebuilding nothing nested, leaving node-pty exactly as broken. The older in-place form still works as a fallback, cd "$(npm root -g)/glimmervoid" && npm rebuild node-pty --dangerously-allow-all-scripts, and is no wider in practice, since a rebuild is scoped by its package argument and only node-pty's scripts run either way. Security note: on the install command that broad flag IS wider, running install scripts for every package in the dependency tree during that one command, bounded only by the fact that the tree is exactly this repo's pinned package-lock.
On Windows a failed attempt can leave a partial node_modules\glimmervoid under the global prefix that npm then reports as EPERM when you retry; remove it with Remove-Item -Recurse -Force "$(npm root -g)\glimmervoid" first. Linux needs no such cleanup: an EALLOWSCRIPTS failure dies inside npm's cache temp clone and leaves the prefix untouched, so rerunning the install on top of it works.
With the binding missing, the server refuses to start instead of crashing partway through boot, and prints that same repair command. glimmervoid doctor runs the same check alongside the rest of your install.
Or clone and run it in place:
git clone https://github.com/johncwaters/glimmervoid.git
cd glimmervoid
npm ci
npm run build
npm startOpen http://localhost:3000 to view the dashboard.
To update a clone, pull and rebuild (Glimmervoid's own startup update check nudges you with the same three steps; --ff-only is the safer form of the pull):
git pull --ff-only && npm ci && npm run buildThen restart the server (systemctl --user restart glimmervoid where the service is installed). See docs/distribution.md for the full picture.
The Dockerfile is a courtesy preview path, not the recommended daily path. The container has no authentication and must only ever be published to localhost.
docker build -t glimmervoid .
docker run -e GLIMMERVOID_HOST=0.0.0.0 -e GLIMMERVOID_INSECURE_BIND=1 -p 127.0.0.1:3000:3000 glimmervoidThe two env vars make Glimmervoid bind all interfaces INSIDE the container (docker port mapping cannot reach the container's loopback); the -p 127.0.0.1:... prefix is what keeps the host side loopback-only. Never publish the port wider. Claude Code credentials and repos must be mounted for real use.
glimmervoid # Start on default port 3000
glimmervoid doctor # Diagnose install / PATH issues and exit
glimmervoid pair # Mint a single-use pairing link for a remote device
glimmervoid pair --list # List paired devices
glimmervoid pair --revoke <id> # Revoke a paired device
glimmervoid pair --name <label> # Label the device being paired
glimmervoid --port 3001 # Override the server port (default: 3000)
glimmervoid --config <path> # Path to config file (default: ~/.glimmervoid/config.json)
glimmervoid --version # Show version number
glimmervoid --help # Show help
Open http://localhost:3000 to view the dashboard.
Remote access is off unless you add a remote block to config.json; it opens a second loopback listener designed to sit behind a reverse proxy such as tailscale serve, never a wider bind. Pair a device with glimmervoid pair, which prints a single-use URL valid for 10 minutes and sets an auth cookie on redemption; glimmervoid pair --list and glimmervoid pair --revoke <id> manage devices, and a revoke takes effect without a restart.
glimmervoid pair --name phone
glimmervoid pair --list
glimmervoid pair --revoke <device-id>Pairing grants real access to your machine; see Limitations for the whole trust boundary.
The install succeeded, but the directory where npm placed the glimmervoid command is not on your PATH (common with a zip/standalone Node, a locked-down corporate image, or pnpm without pnpm setup). To fix it:
-
Confirm it installed:
npm ls -g glimmervoid -
Find npm's global command directory:
npm config get prefix. On Windows the command shims (glimmervoid.cmd,glimmervoid.ps1) live directly in that directory. -
Make sure that directory is on your PATH. The official Node.js Windows installer adds it for you; if you installed Node from a zip, add it in PowerShell, then open a NEW terminal:
[Environment]::SetEnvironmentVariable("PATH", [Environment]::GetEnvironmentVariable("PATH","User") + ";$(npm config get prefix)", "User")
-
Using pnpm? Run
pnpm setuponce (it configures and registers the global bin directory), then reinstall. -
Once
glimmervoidresolves, runglimmervoid doctorto confirm PATH, the native module, and the config path are all healthy.
- Focus view: a single-session work surface with a roster rail of every session beside a worktree review sidebar
- Per-session git worktree isolation: review and merge each agent's committed work from the dashboard while it keeps running
- Spawn and manage multiple Claude Code sessions simultaneously
- Real-time terminal output via xterm.js with WebGL acceleration
- Structural status detection: hooks as the authoritative signal, an OSC-0 title fallback, never screen scraping (see below)
- Background sub-agent completion gate: a session with live background agents or tasks stays out of Complete until they finish
- Native browser notifications when a session needs input, finishes, or fails (opt-in Windows toast fallback)
- Phone layout as a first-class second layout, not a squeezed desktop: Board, Terminal, Review, Radar, PRs and Usage screens, attention-first ordering, and soft-keyboard handling that resizes the terminal instead of covering it
- Remote mode (opt-in): a separate listener with single-use device pairing and cookie auth (see Remote access)
- Telegram notifications (opt-in): pings your phone only when no dashboard tab is open anywhere, so it fills the gap instead of duplicating the browser notification
- Image upload from the phone key strip: pick an image, and its saved path is pasted into that session's prompt for you to send
- Keyboard navigation: jump between sessions, step through the ones needing attention, and merge or resolve from the keyboard
- GitHub PR auto-review (opt-in): reviews your own open PRs headlessly, comments its findings, and auto-merges only the clean PRs whose checks are green
- Radar error monitoring (opt-in): polls PostHog error tracking, pings Telegram the moment an issue spikes, regresses, or first appears, and sends a headless agent to diagnose it and write a report
- Radar auto-fix (opt-in): a spiking, regressed, or new issue gets an agent that reproduces the bug first, repairs it in a throwaway worktree, and hands back a pull request Glimmervoid opens for you; the agent can never push or merge
- Auto-resume by default: sessions that were live when Glimmervoid stopped come back on the next start with their Claude conversation resumed
- Configurable themes, hot-reloadable configuration
The obvious way to know if a Claude Code session finished, is waiting on you, or is still working is to scrape the terminal: watch for a prompt string, a spinner glyph, some text pattern. It breaks constantly. Every TUI redraw, every theme change, every Claude Code release that adjusts spacing invalidates the scrape. Glimmervoid never does this.
Instead, at spawn Glimmervoid injects Claude Code hooks scoped to that one session, no changes to the target repo, that POST to a local HTTP endpoint on every lifecycle event: prompt submitted, turn stopped, notification raised, sub-agent started or finished. These hooks are the authoritative signal. An OSC-0 terminal title fallback (spinner glyph = working, idle glyph = ready) covers the gap for anything that predates or bypasses the hooks. The two are merged with explicit precedence (hook beats title) and a short conflict window so a racing signal can still win before the UI settles.
That design didn't arrive whole. Three incidents shaped it:
/clear and /compact fire no UserPromptSubmit and no Stop, but the terminal redraw briefly flashes a spinner then an idle glyph in the title. Early on, that flash looked exactly like a finished work cycle, so Glimmervoid fired a "session complete" notification on every /clear. (The bug report was, more or less, "why did my terminal congratulate me for clearing the screen.") The fix: on a detected clear/compact, reset both signal sources and mute title-only signals until the next real prompt.
A background sub-agent (launched via Task with run_in_background, or Ctrl+B) can still be running when the main agent's own turn ends and its Stop hook fires. Treating that Stop as completion closed the card while real work was still happening in the background. The fix is a completion gate: Glimmervoid counts live sub-agents from SubagentStart/SubagentStop and reconciles that count against background_tasks, a field Claude Code's own hook payloads declare independently. Staleness between the two is resolved by a sequence number, not a timestamp, because concurrent signals routinely land in the same millisecond.
Boot auto-resume (reattaching a session's Claude conversation after Glimmervoid restarts) depends on capturing Claude's session id from a hook. It was wired to capture that id from SessionStart, which seemed reasonable until testing showed Claude Code doesn't reliably fire SessionStart on interactive startup at all, silently dead in production, no error, resume just never happened. The fix: capture the session id from whichever main-agent hook arrives first, since they all carry it.
Every session also writes a JSONL forensic recording by default (hook payloads and state transitions, not raw terminal bytes), and a version-aware replay harness drives recorded traffic back through the detection code as regression fixtures. That's how bugs like the ones above get diagnosed from real session data instead of guesswork, and how they stay caught if the logic regresses.
- Pure-core seam architecture: IO-free decision logic lives in
session/core/and*-core.tsmodules; thin shells around them do the actual I/O. node:testsuite intests/, zero test-framework dependency.- Table-driven state machines, e.g.
session/core/state-machine.ts. - Fail-closed PR auto-review merge gate:
server/core/pr-review-core.tsonly merges a clean, non-stale, green-checks PR; anything ambiguous (no checks, agherror, a touched workflow file) blocks instead of guessing. - Server-side fix handoff in the Radar lane: the fix agent may only commit locally (
git pushand everyghcall are denied it, because a prefix deny-list cannot constrain a push target), soserver/posthog-wiring.tsdoes the push and opens the pull request from arguments it built itself, refusing any diff that touches.github/workflows/. - Bounded-retention session recorder:
session/session-recorder.ts, capped by file size, file count, and age so it can run unattended indefinitely.
Glimmervoid centers on one session at a time. A left roster rail lists one pill per session (grouped by project, with a live working heartbeat and a "needs you" queue); the center borrows that session's live terminal as the work surface; a right review sidebar shows its changes.
Every git-repo session runs in its own git worktree forked from the integration branch (integrationBranch, default develop), so an agent's edits stay out of your main checkout until you review them. The sidebar splits Committed (the mergeable unit) from Uncommitted work, keeps the diff live, and merges into the integration branch with one click while the session keeps running. If a merge hits conflicts it parks, and Resolve in session hands the conflict back to the agent that owns the worktree with a ready-to-run prompt.
Navigate it all from the keyboard: Alt+1..Alt+9 jump to a session, Alt+Up/Alt+Down move through the rail, Alt+W steps through the sessions needing attention, and Alt+M / Alt+R merge or resolve the selected one. Press ? for the full list.
On first run, Glimmervoid creates ~/.glimmervoid/config.json with defaults. You can also configure from the dashboard Settings button.
{
"port": 3000,
"projects": [
{ "name": "my-project", "path": "C:\\path\\to\\project" }
],
"repoRoots": ["C:\\path\\to\\repos"]
}This is a minimal starting example. The full key list (integrationBranch, autoResume, prReview, posthog, detectBackgroundAgents, recordSignals, and more) is documented in the dashboard's Settings dialog and can also be edited directly in config.json.
Two credentials can come from the environment instead, which keeps them out of config.json altogether:
GLIMMERVOID_POSTHOG_API_KEYsuppliesposthog.apiKey.GLIMMERVOID_TELEGRAM_BOT_TOKENsuppliestelegram.botToken.
Either one overrides the stored value, and Glimmervoid strips that key from config.json on every write, so a save from the dashboard cannot put it back. The systemd unit installed by the dotfiles reads both from ~/.glimmervoid/secrets.env when that file exists.
- Node.js >= 22.18.0 to run Glimmervoid (clone path), which is where type stripping is on by default, so the
.tssources and the.test.tssuite run unbuilt;node:sqliteFTS5 for the long-term memory store lands earlier, at 22.16.0. The standalone CLI install needs Node.js >= 22.22.2, because it goes through npm 12 and that is npm 12's own engine floor (npx npm@12fails the same check on an older Node). Distro-packaged Node is usually far older than either floor; use nodesource or nvm. - Windows 11 or Linux
- Claude Code CLI installed and available on PATH
- Linux build tools for node-pty:
sudo apt install build-essential python3
npm install
npm run dev # Vite dev server with HMR (port 5173)
npm run build # Production build to dist/
npm start # Production servertests/ is the automated node:test suite (npm test). test/ holds manual smoke scripts, run by hand, not part of CI.
- Windows 11 and Linux. Developed daily on Windows 11, which is where the multi-session tooling didn't exist; it also runs on Linux servers, which is how my always-on machines are provisioned. macOS is untested, not merely undocumented.
- Local-first, with an opt-in remote door. By default Glimmervoid binds
localhostand neither WebSocket channel has authentication, so any local process can connect. That's a deliberate single-user scope choice, not an oversight, but it means the local port must never be exposed to the network. Remote access is a separate opt-in listener gated by single-use pairing tokens and cookies, meant to sit behind a reverse proxy such astailscale serve; a pairing cookie grants full code execution as the server account, so pairing URLs are passwords and should be handled like them. - Requires the Claude Code CLI. Glimmervoid spawns and manages it; it doesn't replace it.
See CHANGELOG.md for release history.
