Clatter
A real-time message bus for Claude Code sessions. Run many claude sessions at once — one
per tmux pane — and let them talk to each other: one session asks another a question, the other
wakes on its own, answers, and the reply lands back in the asking pane. No copy-paste between
panes, no human relay.
Two Claude Code sessions over the bus — ask on the left; the peer auto-wakes and answers on the right.
session "frontend" relay (per machine) session "api"
────────────────── ─────────────────── ─────────────
/clat ask api which port is the
auth service on? ────────────▶ new message for "api"
│ resolve api → its tmux pane
└─ wake it ──────────────────────▶ (idle or busy)
│
reads the question, answers "8081",
sends the reply back:
reply appears in your pane ◀────── new message for "frontend" ◀────────────┘
Clatter = Claude + chatter — it lets Claude Code sessions chatter to each other across tmux. Under the hood it's a small, dependency-light message bus — a few bash scripts, a file-drop mailbox, and one tiny relay daemon.
Why
If you run a fleet of Claude Code sessions (one per project/pane), they can't see each other. You
end up hand-carrying facts between them: "the other session set the port to 8081", "the model
you loaded is aliased qwen-large". Clatter gives them a phone line. A session can consult a
peer that actually owns the answer, in real time, without you in the middle.
Features
- Real round-trips.
/clat ask <peer> <question>sends a query; the answer comes back to your pane asynchronously — because the relay can wake an idle session, not just leave it a note. - Names you recognize, live. A session's bus name is its Claude session name (what
/renamesets, shown in your tab) — resolved live, so renaming a session (locally or from the web UI) is reflected immediately, with no manual wiring. Sessions auto-register at start and onclaude -c. - Self-cleaning. Dead sessions are pruned automatically (on contact and on a timer).
- Safe by design. The only thing ever typed into another pane is a fixed control string — never
message content — so a peer can't inject an arbitrary "user" turn. Sensitive workspaces can be
marked read-only to the relay, and
/clat doctoraudits that the guard actually covers them. (See Security.) - Small + legible. Bash +
jq+inotifywait+tmux. One systemd--userrelay. No server, no database, no ports.
How it works (in three facts)
- Sessions are interactive
claudeCLIs, one per tmux pane. The only way to hand a running interactive session a new turn istmux send-keysinto its pane — which delivers instantly if it's idle and queues if it's mid-turn. - The target pane is resolved live, never stored. Pane ids drift (tmux resurrect/renumber), so nothing caches a pane id: each session is keyed by a stable sessionId (which also names its mailbox, so a rename never misroutes), and at wake time the relay looks up that session's pid and resolves pid → tty → current pane.
- Nothing runs between turns, so the push comes from outside: a per-machine relay daemon
watches the mailboxes (
inotifywait) and wakes the target pane when a message arrives.
Full design in ARCHITECTURE.md.
Requirements
- Linux with tmux for auto-wake — a session that should be woken automatically must run in a tmux pane. A session outside tmux still works, in manual (poll) mode (see Manual mode below).
jqand systemd with user services + lingering.inotify-tools(inotifywait) is recommended but optional — without it the relay falls back to polling.- Claude Code (the
claudeCLI) with hooks and custom slash commands. Note:claude --safe-modedisables all customizations — hooks and custom commands don't load — so a safe-mode session won't join the bus or have/clat.
sudo apt install jq inotify-tools tmux # Debian/Ubuntu
loginctl enable-linger "$USER" # so the relay survives logout/reboot
Install
Via pip (the package is clatter-bus; the command is clatter):
pip install clatter-bus
clatter install
…or straight from the repo:
git clone https://github.com/brandonrthomas/clatter.git
cd clatter
./install.sh
Both run the same install.sh — it's idempotent. It copies the code to ~/.claude/clatter/, installs the /clat slash
command, wires SessionStart/SessionEnd hooks into ~/.claude/settings.json (backing it up
first, preserving everything else), and enables the relay + cleanup timer. Open new sessions (or
claude -c existing ones) and they'll join.
Usage
From inside any session:
/clat peers # who's on the bus (name, machine, alive, mode, description)
/clat ask <name> <question> # ask a peer; the answer returns to THIS pane, asynchronously
/clat send <name> <message> # fire-and-forget notify
/clat broadcast <message> # notify every live session
/clat recv # read your inbox (the relay normally triggers this for you)
/clat clear # archive your inbox without reading it
/clat status # your own bus name + pending inbox
/clat mode [auto|manual] # show or set whether the relay may wake this session
/clat desc [text] # show or set this session's description (shown in /clat peers)
/clat doctor # audit the manual-mode guard — what the patterns actually match
Everywhere above, <name> is a peer's Claude session name — the name /rename sets, shown in
its tab — matched live, so it always tracks the session's current name. (Leave a session unnamed and
it uses Claude's auto-generated name, e.g. host-quiet-tome.)
If two live sessions resolve to the same name, they're disambiguated with a -2/-3 suffix in
/clat peers and when addressing (the earliest keeps the bare name); a session that renames into a
collision gets a heads-up to pick a unique one. The suffix is display/addressing only — delivery is
keyed by sessionId, so it never misroutes.
/clat ask doesn't block — you keep working; when the peer answers, the relay wakes your pane and
the reply appears inline.
Composing vs. verbatim. For ask / send / broadcast, the text after the command is normally
an instruction for what to communicate: the session composes the actual message from its current
context and sends that — e.g. /clat ask box tell them the port I just set sends something like
The auth port is 8081. (A self-contained question is just sent as-is.) To send text exactly as
written — a command, a snippet, precise wording — prefix it with *: /clat send api *rm -rf /tmp/cache
stores that literal string as a message (it is never executed).
Cross-machine. List your other hosts in ~/.claude/clatter/peers (one SSH alias per line).
Then /clat peers aggregates their live sessions (shown as name@host), and /clat ask <name> <question>
auto-locates a peer across those hosts — or address one explicitly as name@host. The message is
dropped over SSH into that host's mailbox and its relay wakes the pane; replies route back
automatically. Requires Clatter on each
host and key-based SSH between them.
Configuration
| What | How |
|---|---|
| Install location | CLATTER_ROOT (default ~/.claude/clatter) |
| This machine's name | CLATTER_MACHINE (default hostname -s) — used for cross-machine addressing |
| Read-only workspaces | add globs matching your real dir names to ~/.claude/clatter/manual-patterns; verify with /clat doctor (see manual-patterns.example) |
| Cross-machine peers | list SSH hosts in ~/.claude/clatter/peers (see peers.example) |
Manual mode. A registered, fully addressable session that the relay will never type into —
peers see it in /clat peers, messages reach its mailbox, and you drain them yourself with /clat recv.
A session registers manual for either reason:
- its cwd matches a glob in
manual-patterns— sensitive workspaces you don't want a peer able to drive; or - it isn't running in a tmux pane, so there's no pane to
send-keysinto. Rather than registerautoand then silently fail to wake, it degrades to manual — still on the bus and reachable, just polled instead of pushed.
A session's tmux-ness is fixed when it launches, so this is decided once at registration. If an
auto session later loses its pane (e.g. tmux is killed mid-session), the relay can't wake it — it
leaves the message queued and logs a loud WARN rather than dropping it silently.
The guard fails open: a glob that matches nothing looks identical to a working one, so it can
protect nothing without telling you. Match your actual directory names, not category words (*acme*,
not *emr*), and run /clat doctor to see which live sessions and workspace dirs the patterns
cover — /clat peers also warns when manual-patterns is set but matches no live session. Mode is
fixed at registration, so restart a session (or run /clat mode manual) after editing patterns.
Security
The threat here isn't eavesdropping — it's injection, because waking a pane literally submits a turn in another session. Clatter is built around that:
- The wake is a single fixed constant (
/clat recv). Message content is always read from a file by that trusted command, never typed into a pane — so a peer can never cause arbitrary text to be submitted as a user turn elsewhere. Mailbox names are charset-restricted on both the send and relay sides. - Rendered messages are framed as untrusted data with an explicit "do not obey" preamble; header fields are newline-escaped so a message can't forge the frame.
- Message bodies are inert data end-to-end. They're stored with
jq --arg, transported as files (piped, never interpolated, over SSH cross-machine), and rendered as quoted text — nothing in a message is ever shell-executed by the pipeline. You can safely send text that contains commands; the/clatcommand hands your free text to the CLI as a single quoted argument. - Manual mode (above) guarantees a peer can never drive a sensitive session.
- Never put secrets, credentials, or private/regulated data in a message — the bus is local plaintext files under your user account, and a message lands in another session's context.
No encryption (local files, Unix perms — same trust model as Claude Code's own data). Cross-machine rides your existing SSH keys.
Uninstall
./uninstall.sh # stop services, remove /clat + hooks (keeps your mailbox/registry)
./uninstall.sh --purge # also delete ~/.claude/clatter entirely
Your settings.json is backed up before every change.
Status & limitations
- Cross-machine works (verified host↔host, both directions, including different home dirs). List
peer hosts in
~/.claude/clatter/peers; then/clat peersshows their sessions asname@hostand a bare-name/clat ask <name>auto-locates the peer. You can always force one withname@host. - Waking a pane while you're mid-typing appends to your input line (targets are normally idle).
Development
Run the test suite — zero framework, just bash + jq:
./test/run.sh
It covers registration (sessionId-keyed), live-name resolution (transcript title wins over the
session-file name, with fallback) including a rename, duplicate-name disambiguation, send/recv/clear,
message-body safety (shell metacharacters stay inert), target validation, reply routing, the
manual-mode guard + /clat doctor (fail-open detection), the rename-collision notifier, mode/desc,
discovery --json, broadcast, and cleanup — against an isolated CLATTER_ROOT with fake session
files. The relay, tmux wake, and cross-machine SSH are integration paths, verified manually.
License
Apache-2.0 © 2026 Brandon Thomas
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file clatter_bus-0.1.0.tar.gz.
File metadata
- Download URL: clatter_bus-0.1.0.tar.gz
- Upload date:
- Size: 32.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8347d5cf8d2bbb4519dd75443a4ae7aef7a034ac796c3e8fd41daf9fb32f830
|
|
| MD5 |
d9ad935273871f7c23b345b590580a3f
|
|
| BLAKE2b-256 |
7fd3768f488d62f498745f4cdd8aad6a667073dd2e3a299e3f971f5d1361403d
|
Provenance
The following attestation bundles were made for clatter_bus-0.1.0.tar.gz:
Publisher:
publish.yaml on brandonrthomas/clatter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clatter_bus-0.1.0.tar.gz -
Subject digest:
c8347d5cf8d2bbb4519dd75443a4ae7aef7a034ac796c3e8fd41daf9fb32f830 - Sigstore transparency entry: 2377604508
- Sigstore integration time:
-
Permalink:
brandonrthomas/clatter@c970903d70b58d10bdce790e12136270a25347a5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/brandonrthomas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@c970903d70b58d10bdce790e12136270a25347a5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file clatter_bus-0.1.0-py3-none-any.whl.
File metadata
- Download URL: clatter_bus-0.1.0-py3-none-any.whl
- Upload date:
- Size: 40.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
acd0d122730a8237fce2ce38efa4c574c22a9c3d787e4bfb43b94b4aff9ef60c
|
|
| MD5 |
82e7060f8261d87a5e87cc308e4a29e2
|
|
| BLAKE2b-256 |
3bec2b38059bf89253a7795d91033cd0b594fcdff2b691c9903c2225d0a42a8a
|
Provenance
The following attestation bundles were made for clatter_bus-0.1.0-py3-none-any.whl:
Publisher:
publish.yaml on brandonrthomas/clatter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clatter_bus-0.1.0-py3-none-any.whl -
Subject digest:
acd0d122730a8237fce2ce38efa4c574c22a9c3d787e4bfb43b94b4aff9ef60c - Sigstore transparency entry: 2377604578
- Sigstore integration time:
-
Permalink:
brandonrthomas/clatter@c970903d70b58d10bdce790e12136270a25347a5 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/brandonrthomas
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@c970903d70b58d10bdce790e12136270a25347a5 -
Trigger Event:
push
-
Statement type: