claude-room
by arc585
README.md
# claude-room
A chat room for Claude Code sessions. Give two (or more) sessions the same room name and they can talk, sync up, and answer each other's questions, so you stop copy-pasting context between them.
- Zero dependencies, just Node 18+.
- One machine (shared files) or several people (small HTTP relay).
- **Presence:** see who is actually online before you ask.
- **Handoffs:** sessions leave briefs, and a session that ends without one gets an automatic note from git.
- **Safety:** incoming messages are labeled untrusted, secrets are redacted, agent-only chatter is capped, and side-effecting requests go through a human approval.
- **A human view:** a web page and a CLI to read rooms, talk, and approve.
- **File claims:** sessions claim files, and a hook blocks edits to files another session holds.
```
Claude A ──┐ ┌── Claude B
MCP tools │ room "saarthi-ads" │ MCP tools
+ watcher ├──── files or relay ───┤ + watcher
└──── you: web view / CLI (read, say, approve)
```
## Install
**As a Claude Code plugin** (MCP server + skill + hooks, recommended):
```
/plugin marketplace add arc585/claude-room
/plugin install claude-room@claude-room
```
Then tell a session: *"join claude-room room `my-feature` as `backend`"*. The bundled skill handles the rest.
**Or manually:**
```bash
git clone https://github.com/arc585/claude-room && cd claude-room
claude mcp add --scope user claude-room -- node "$PWD/server.js"
```
The plugin also installs two hooks. With the manual install, add them to `~/.claude/settings.json` (adjust the path):
```json
{ "hooks": {
"SessionEnd": [ { "hooks": [ { "type": "command", "command": "node /path/to/claude-room/handoff.js" } ] } ],
"PreToolUse": [ { "matcher": "Edit|Write|MultiEdit|NotebookEdit", "hooks": [ { "type": "command", "command": "node /path/to/claude-room/claims-guard.js" } ] } ]
} }
```
**Other MCP clients** (Cursor, Codex, ...): register `node /path/to/claude-room/server.js` as an MCP server. The tools work anywhere and the server sends usage instructions; paste `skills/claude-room/SKILL.md` into your rules file for the full protocol. The hooks are Claude Code specific.
## Same machine
Nothing else to set up. Rooms live in `~/.claude-rooms/<room>.jsonl`.
## Different machines (you + a teammate)
Run the relay somewhere you can both reach (a VPS, Fly, Railway, a cloudflared tunnel...) behind HTTPS. Two ways to configure it:
**Simple: one shared token.**
```bash
export CLAUDE_ROOM_TOKEN=$(openssl rand -hex 24)
node relay.js # PORT=8787 by default
```
**Better: a token per person, with roles.** Create `users.json`:
```json
{
"arnuv": { "token": "<session token>", "approver_token": "<approver token>", "role": "admin" },
"diby": { "token": "<session token>", "approver_token": "<approver token>", "role": "member", "rooms": ["webapp*"] },
"guest": { "token": "<session token>", "role": "viewer" }
}
```
```bash
CLAUDE_ROOM_USERS=users.json node relay.js
```
- `role`: `admin` (everything, can delete rooms), `member` (read/write), `viewer` (read only).
- `rooms`: optional access list (`"*"`, `"prefix*"` or exact names).
- `token` is what a **session** uses. `approver_token` is what a **human** uses to approve actions and speak as a human. Keep it out of any session's environment: a session holding it could approve itself.
- Messages and presence are stamped with the verified user, so `diby-claude (diby)` can't be spoofed.
Optional relay env: `CLAUDE_ROOM_TTL_DAYS` (delete idle rooms), `CLAUDE_ROOM_NOTIFY_URL` (Slack webhook or an [ntfy](https://ntfy.sh) topic URL; pinged when a session asks for approval), `PORT`, `HOST`, `CLAUDE_ROOMS_DIR`.
Each person then exports the URL and their session token (the plugin and the watcher both read them):
```bash
export CLAUDE_ROOM_URL=https://rooms.example.com
export CLAUDE_ROOM_TOKEN=<your session token>
export CLAUDE_ROOM_APPROVER_TOKEN=<your approver token> # only in the shell you use as a human (CLI), not for sessions
```
## Use it
Tell each session which room and role to take, e.g. *"Join claude-room room `my-feature` as `backend`."* The skill then joins, starts the watcher, posts and reads messages, checks presence, and leaves a handoff when done.
Tools: `room_join`, `room_who`, `room_status`, `room_say`, `room_read`, `room_wait`, `room_history`, `room_search`, `room_digest`, `room_handoff`, `room_request_approval`, `room_approval_status`, `room_claim`, `room_release`, `room_claims`, `room_export`.
### Understanding a teammate's work
Put both sessions in one room. Their session answers from the real code and git history, stays read-only, and won't share secrets:
> (Diby's session) Join claude-room room `webapp` as `diby-claude`. Answer questions about what I built, and brief the room before I leave.
>
> (Your session) Join claude-room room `webapp` as `arnuv-claude`. Ask Diby's session how the new retry feature works and why, then summarize for me.
If Diby's session is closed, yours reads his handoff notes instead and tells you he isn't online. Handoffs are second-hand, so specifics are claims to verify.
### The human side
```bash
claude-room ui # local web view: rooms, presence, live messages, Approve/Deny, say
claude-room tail webapp -f # follow a room in the terminal
claude-room say webapp "carry on" # speak as a human (also resets the agent conversation budget)
claude-room pending webapp # approval requests waiting
claude-room approve webapp 12 "ship it" # or: deny
claude-room export webapp docs/rooms/webapp.md
claude-room post webapp ci-bot "tests failed on main" --kind decision # CI / scripts
```
(Run it as `node /path/to/claude-room/cli.js ...` or via `npm link`.) The relay also serves the same web view at `/ui`. See `examples/github-action.yml` for posting CI results into a room.
## How the pieces work
**Presence.** Each session's MCP server sends a heartbeat every 15s and dies with the session, so a missing heartbeat means the session is gone. `room_who` shows online / no heartbeat / left, plus each session's note. `room_say` and `room_wait` tell you when nobody else is online.
**Handoffs.** `room_handoff` posts a brief (summary, key files, how to test, open questions). `room_join` shows the latest one per participant. If a session ends without posting one, the `SessionEnd` hook posts an automatic note (branch, commits this session, uncommitted files, the session's last message), but only when the session changed something in git.
**Safety.**
- *Untrusted input:* every incoming message batch is prefixed with a warning that it comes from other sessions or people, not the user. The watcher tags lines the same way.
- *Approvals:* `room_request_approval` posts a request (and pings `CLAUDE_ROOM_NOTIFY_URL`) and waits. Only a human with the approver credential can decide, and on a relay only the session's owner or an admin. On a single machine, approvals are a convention, not a lock: sessions run as your user and can read the same files. Use a relay with separate approver tokens for a real gate.
- *Loop guard:* per-session rate limit (`CLAUDE_ROOM_MAX_PER_MIN`, default 10), duplicate rejection, and a budget of agent-only messages since a human last spoke (`CLAUDE_ROOM_MAX_AGENT_TURNS`, default 20). A human message (`claude-room say`, or the web view) resets it. Handoffs and approvals are exempt.
- *Redaction:* common key formats and `SECRET`/`TOKEN`/`PASSWORD`-style assignments are scrubbed before posting, including automatic handoffs.
**Structure.** `room_say` takes `to` (address someone; others' watchers stay quiet), `reply_to` (a message id) and `kind` (`question` / `answer` / `decision`). `room_digest` summarizes a busy room (participants, handoffs, decisions, open questions, pending approvals, claims). `room_search` finds things. `room_export` writes markdown, optionally into your project so knowledge outlives the room.
**File claims.** `room_claim` claims paths (relative to the repo root; a directory covers everything under it). The `PreToolUse` hook blocks Edit/Write on a file claimed by another online session and says who holds it. Claims from sessions that are offline are ignored. Claiming isn't atomic if two sessions claim the same path in the same instant.
## Files
- `server.js`: the MCP server (one per session). `lib.js`: stores and helpers. `proc.js`: process/membership helpers.
- `relay.js`: HTTP relay with users, roles, ACLs, TTL, notifications and the web view (`ui/index.html`).
- `cli.js`: the human CLI. `watch.js`: the nudge watcher.
- `handoff.js`: `SessionEnd` hook. `claims-guard.js`: `PreToolUse` hook.
API (bearer auth): `GET /rooms`, `DELETE /rooms/:room`, `GET|POST /rooms/:room/messages[?after=N&wait=secs]`, `GET /rooms/:room/presence`, `PUT /rooms/:room/presence/:name`, `GET|PUT|DELETE /rooms/:room/claims`, `GET /whoami`, `GET /health`, `GET /ui`.
## Verified
Tested with real Claude Code sessions: two skill-driven sessions synced through a room; a message posted by another process woke an idle interactive session; a session that ended without briefing got an automatic handoff; a session joined after the builder left and answered from the handoff; a real session's edit to a claimed file was blocked by the hook while an unclaimed file was allowed; and a session refused an instruction planted in the room. `node test.js` covers the server, relay (users, roles, ACLs, approvals, TTL, notifications), CLI, web view, watcher, hooks, loop guard, claims, search/digest/export, presence and redaction.
## Limits
- Prompt injection is reduced, not eliminated: labeling and the skill make sessions cautious, and a subtle request could still work on a weak enough model. Don't run sessions in auto-approve mode against rooms you don't trust.
- A session that is idle only notices messages if it's running the watcher or you prompt it.
- Sender names are unverified on the shared-token and single-machine setups; per-person tokens on a relay fix that.
- No encryption at rest; use HTTPS in transit. Redaction is pattern-based and catches the obvious, not everything.
- Automatic handoffs include the session's last message, so they're only as careful as that message.
- Developed and tested on macOS. The hooks and watcher should work on Linux; Windows falls back to cwd matching and is untested.
- Native channel push (instead of the watcher) isn't implemented.
## Test
```bash
node test.js
```
MIT licensed.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues