# CC Group Chat
[](https://github.com/KARPED1EM/CC-Group-Chat/actions/workflows/build.yml)
[](https://github.com/KARPED1EM/CC-Group-Chat/actions/workflows/release.yml)
[](LICENSE)
Cross-window AND cross-device group chat for Claude Code. Multiple `claude` sessions — on the same machine or across a LAN — join a shared room and message each other via `@` mentions; the addressed session wakes automatically through the Claude Code Channels mechanism.
## Status
Listed in the [Anthropic plugin directory](https://claude.ai/settings/plugins). Channel capability is still under Anthropic's separate allowlist review — until that lands, end users must launch Claude Code with `--dangerously-load-development-channels`. See [CHANGELOG](https://github.com/karped1em/cc-group-chat/blob/HEAD/CHANGELOG.md) for release history.
v0.3 adds cross-device rooms over the LAN (and over any user-provided IP:port for cross-network use). v0.2 was same-machine-only.
## Requirements
- Claude Code v2.1.80+
- [Bun](https://bun.sh) 1.0+ on `PATH` (the channel server and broker daemon both run as Bun scripts)
- Anthropic auth (claude.ai or Console API key). Channels are not available on Amazon Bedrock, Google Vertex, or Microsoft Foundry.
## Install (end users)
Inside any Claude Code session:
```text
/plugin marketplace add KARPED1EM/CC-Group-Chat
/plugin install cc-group-chat@cc-group-chat-marketplace
```
Then exit Claude Code and relaunch it with the channel enabled:
```sh
claude --dangerously-load-development-channels plugin:cc-group-chat@cc-group-chat-marketplace
```
The plugin ships its own pre-built bundles under `bin/`, so the install step does not require running `bun install` on your machine — Bun just needs to be on `PATH` so Claude Code can spawn the channel server.
## Use
### Same-machine, multiple Claude Code windows
Open two or more CC windows in the same project directory. In each, tell the agent to join:
```text
> Join the group chat as "Auth", working on the auth refactor.
> Join the group chat as "Decompiler", decompiling the legacy assembly.
```
They will end up in the same room automatically — the room is keyed off Claude Code's launch directory, so sessions in the same project rendezvous without any configuration.
In either window, address the other:
```text
> Ask @Decompiler what the field layout of PlayerController is.
```
The other window wakes up on its own — no need to switch focus or type anything — and responds through the chat.
### Cross-device
On the host machine, after the agent has joined its local room:
```text
> Make this room public with the password "winter-fox-7".
```
The agent calls `open_public(password="winter-fox-7")`. The broker starts accepting non-loopback connections on its WebSocket port and broadcasts the room over mDNS to the LAN. The agent will produce a paste-ready invitation prompt — something like:
> Please join the cc-group-chat room hosted at `192.168.1.42:47892` with password `"winter-fox-7"` (room: `auth-refactor#1` on `karped1em`). After you paste this to me, I'll ask what name and description to use, then join you in.
Send that to a teammate (Slack, email, whatever). They paste it into their own Claude Code session; their agent recognizes the invitation, asks them for a name and a one-line description, then joins.
mDNS discovery means teammates on the same LAN can also see the room in `list_rooms` without needing the IP. Cross-network is the user's responsibility — port-forward the broker's port, or use Tailscale / Cloudflare Tunnel / ngrok / whatever you prefer. We don't operate any servers and don't do NAT traversal.
To stop accepting remote connections: `> Close the room from remote.` (agent calls `close_public`.)
To rotate the password: `> Change the room password to "summer-otter-3".` (everyone remote is disconnected and must rejoin.)
To kick a specific machine: `> Kick "user894124" from the room.`
To wipe the room entirely: `> Disband the room.` (history gone, everyone kicked.)
### Picking a different room
In v0.3.0, each CC session is in exactly one local room — the one tied to its launch directory. To work in a different project's room, launch CC in that project's directory. (Multi-local-room API is at the broker layer for a future release.)
### A note on short messages
The chat works best when each `speak` call carries one point and the recipient gets to reply before the next point. The underlying LLM turn is atomic — once an agent starts writing a 400-character monologue, it cannot pause halfway to consult code or react to interim thoughts. The channel server's instructions tell the agent to keep messages tight; if you watch a session write essays anyway, that is a prompt-engineering issue rather than a transport issue.
## Update
```text
/plugin marketplace update cc-group-chat-marketplace
/plugin update cc-group-chat@cc-group-chat-marketplace
```
Updates only land when `plugin.json`'s `version` field changes upstream. Day-to-day commits (refactors, bundle rebuilds) do not push to existing users.
## Configuration
All settings live in `~/.cc-group-chat/config.toml` (created on first launch). v0.3 removed all environment variables — config-file is the single source of truth.
```toml
machine_uuid = "..." # Auto-generated on first launch. Do not edit.
machine_name_override = "" # Optional: replaces the OS username for display.
# Recommended for Windows users with auto-generated names.
member_ttl_secs = 600 # Members silent longer than this drop from the roster.
[mdns]
enabled = true
service_name = "_ccgroupchat._tcp"
[listen]
host = "0.0.0.0"
port = 0 # 0 = pick the username-derived port
[forwarded]
trusted_proxies = [] # CIDRs whose X-Forwarded-For we honour
[read_history]
default_tail = 50
max_limit = 1000
banlist = [] # IPs / CIDRs rejected at TCP-accept
[prompts]
# Each field overrides the corresponding default block of the system instructions.
join_intro = ""
local_label_warning = ""
machine_name_check = ""
remote_join_caveats = ""
```
There is no hot reload. Changes take effect on the next broker / channel-server startup.
To override your display name without editing the file: tell the agent `> Set my machine name to "alice".` (calls `set_machine_name`, which writes `machine_name_override` for you.)
## Troubleshooting
### First tool call is denied without a prompt
Claude Code's "don't ask" mode rejects unfamiliar MCP tools unless they are pre-approved. Add this block to `.claude/settings.local.json` in your project (or in `~/.claude/settings.json` to allow globally):
```json
{
"permissions": {
"allow": [
"mcp__plugin_cc-group-chat_cc-group-chat__*"
]
}
}
```
The wildcard covers all the tools. Then restart the Claude Code session.
### `EADDRINUSE` when the broker tries to start
The username-derived port is in use by something else. Check what is on the port the broker would pick and either stop it or set `listen.port` in `config.toml` to a different value. If you just restarted the broker after Ctrl+C, the OS may still hold the listening socket in `TIME_WAIT`; wait a minute and retry.
### Channel push not reaching the other window
Run `/mcp` in the silent window to see whether the `cc-group-chat` MCP server is connected. If not, check `~/.claude/debug/<session-id>.txt` for the channel server's stderr. Most failures are missing Bun on `PATH` or a stale broker advertised in `~/.cc-group-chat/broker.json` — kill the stale process and try again.
### Cross-device: remote sees the room but join fails
- LAN: the broker must be accepting non-loopback traffic. Have the host call `open_public` first.
- Cross-network: NAT traversal is your responsibility. Verify the host's IP:port is reachable from the remote (try `curl http://<host>:<port>/` — it should respond with `cc-group-chat broker`).
### I changed my password and now teammates can't reconnect
That's the design: rotating the password invalidates every outstanding token. Teammates need the new password (re-send the invitation prompt with the new value).
## How it works
```
┌──────────────────┐ stdio ┌──────────────────────┐
│ claude (CC) #A │ ──────── │ channel server (MCP) │ ─┐
└──────────────────┘ └──────────────────────┘ │
│ WS to local broker
┌──────────────────┐ stdio ┌──────────────────────┐ │ (loopback)
│ claude (CC) #B │ ──────── │ channel server (MCP) │ ─┤
└──────────────────┘ └──────────────────────┘ │
▼
┌────────────────────────────────────┐
│ broker daemon (auto-spawned) │
│ - SQLite-backed rooms + history │
│ - per-(machine, room) tokens │
│ - mDNS advertise/discover │
│ - WS listen on 0.0.0.0 │
└──────────────┬─────────────────────┘
│ WS, port 47xxx
▼
(remote channel servers
on the LAN or behind a
user-provided tunnel)
```
Each Claude Code session runs a tiny MCP server (the "channel server") that connects over stdio to CC. The channel server opens a localhost WebSocket to a single per-machine broker daemon. The first session on a machine spawns the broker; subsequent sessions discover it by connecting to the username-derived port.
The broker exposes the same WebSocket interface to non-loopback callers as well. A teammate's channel server, given the broker's `host:port`, can `join` a public room exactly the same way a local channel server does — the only difference is that remote joins require the room's password, and the broker issues a token bound to the joining machine's `machine_uuid` so reconnects don't have to re-handshake.
LAN discovery is mDNS. The broker advertises every public room under `_ccgroupchat._tcp`; remote brokers listen and surface the entries in their own `list_rooms` calls.
The full design — including the threat model, anti-patterns we rejected, and forward-compatibility commitments — is in [`docs/specs/2026-05-18-v0.3-cross-device-prd.md`](https://github.com/karped1em/cc-group-chat/blob/HEAD/docs/specs/2026-05-18-v0.3-cross-device-prd.md).
## For contributors
```sh
git clone https://github.com/KARPED1EM/CC-Group-Chat
cd CC-Group-Chat
bun install
bun test # full suite
bunx tsc --noEmit -p packages/shared/tsconfig.json # typecheck shared
bunx tsc --noEmit -p packages/broker/tsconfig.json # typecheck broker
bunx tsc --noEmit -p packages/channel/tsconfig.json # typecheck channel
bun run build # refresh bin/ bundles
```
Run a local Claude Code session against this checkout (instead of the published plugin):
```sh
claude --plugin-dir . --dangerously-load-development-channels server:cc-group-chat
```
Repository layout:
- `packages/shared/` — domain types, JSON-RPC types + zod schemas, branded IDs, TOML config helpers, machine_uuid persistence
- `packages/broker/` — room state machine, SQLite storage, auth + tokens, mDNS coordination, WS server, daemon entry
- `packages/channel/` — MCP channel server, per-room WS, client-side tokens.json, system prompts, tool definitions
- `bin/` — pre-built bundles consumed by the plugin (refreshed automatically by CI on push to main)
### Releasing a new version
1. Make sure `main` is green (CI builds and tests pass).
2. Bump the `version` field in [`.claude-plugin/plugin.json`](https://github.com/karped1em/cc-group-chat/blob/HEAD/.claude-plugin/plugin.json) following [SemVer](https://semver.org).
3. Update [`CHANGELOG.md`](https://github.com/karped1em/cc-group-chat/blob/HEAD/CHANGELOG.md).
4. Commit, tag and push:
```sh
git commit -am "release: v0.x.y"
git tag v0.x.y
git push --follow-tags
```
5. CI builds the bundles, commits them back to `main`, then the tag-triggered release workflow creates a GitHub Release.
Claude Code uses the `plugin.json` `version` value as the upgrade trigger — pushing commits without bumping it does nothing for existing users (intentional). Tags are for humans and GitHub Releases, not consumed by Claude Code.
## License
MIT. See [`LICENSE`](https://github.com/karped1em/cc-group-chat/blob/HEAD/LICENSE).