| name | claude-chat |
| description | Two Claude Code sessions coordinate via a shared JSONL chat file at ~/Downloads/claude-chat.jsonl, push-style via the Monitor tool. Lighter than /multi-claude:paired-branches; no branches, no worktrees, just a chat channel. Use when two sessions need to talk in real time and GH-issue polling is too slow / too expensive. |
| argument-hint | <my-id> <peer-id> |
/multi-claude:claude-chat: two Claudes, one file, push-style
A minimal recipe for two Claude Code sessions on the same machine to chat through ~/Downloads/claude-chat.jsonl, with each side waking on file change via the Monitor tool instead of polling. Validated on cupertino on 2026-05-19 during the #779 postmortem collab: latency went from 30s–240s (GH issue polling on both sides) to sub-second push.
Use when
- Two Claude Code sessions on the same Mac need to coordinate in real time.
- The collab is short-lived enough that a permanent GH artifact would be coordination noise.
- One process is implementing, another is reviewing / testing / monitoring.
- You need a transcript you can
tail -f for human visibility.
Don't use when
- The two sessions are on different machines. The file is local; use a GH issue (or
/multi-claude:paired-branches with worktrees on each machine) instead.
- You want autonomous agent teams that coordinate without a human (use Claude Code's native agent-teams support).
- The collab has long-term documentary value worth keeping in a bug-tracker thread (GH issues outlast files).
- You need cross-machine durability. Files are per-Mac and not git-tracked here.
How it relates to /multi-claude:paired-branches
| /multi-claude:paired-branches | /multi-claude:claude-chat (this skill) |
|---|
| Sets up | Worktrees, branches, and chat file | Chat file only |
| Wake mechanism | Polling loop (tail -F + python jq) | Monitor tool (push via tail-F event stream) |
| Latency | seconds to minutes | sub-second |
| File rotation on start | No (appends to existing) | Yes (archives existing on init; always start fresh) |
| Use case | Branch-coupled implementation + verification with a human moderator | Any two-Claude coordination without the branch ceremony |
paired-branches and claude-chat can compose: use paired-branches when you need the worktree setup, and rely on the chat-file half of that recipe; use claude-chat on its own when you don't.
Architecture in one paragraph
Two Claude Code sessions on the same Mac. A shared JSONL file at ~/Downloads/claude-chat.jsonl, one message per line: {"ts":"<iso-8601-with-tz>","from":"<my-id>","to":"<peer-id>","kind":"<topic>","msg":"<text>"}. Each session arms a Monitor on the file at start, filtered to messages NOT from itself (so own writes don't echo as events). To send: append a JSON line. The counterparty's Monitor fires sub-second on the mtime change, emits the new line as a <task-notification>, and the receiving Claude reads + replies. No daemons, no sockets, no servers, no GH polling.
Why a file (and not a named pipe, socket, GH issue, or MCP server)
- POSIX guarantees
write() with O_APPEND is atomic for writes under PIPE_BUF (4 KB on macOS), so two Claudes appending one-line JSON simultaneously never interleave. No locking required as long as each message is one line under 4 KB.
- File survives either Claude restarting. Pipes and sockets don't.
- Late joiners read history for free.
tail -f gives you a free human-readable transcript view.
Monitor tool can tail -F the file with --line-buffered grep and turn each new line into a push notification: no polling cost, sub-second latency.
- Compared to a GH issue as channel: zero API call per check, zero GitHub rate limit, zero turn burned on a "no change" poll, transcript outside the bug tracker so issue threads stay topical.
Prerequisites
- macOS with
git, bash, date (all default).
- Both Claude sessions on the same Mac.
- Both sessions have shell +
Monitor tool available.
Setup (one time per collab, both sides)
The skill expects you (the invoking Claude) to know your own ID (<my-id>, e.g. c1 or c2) and your peer's ID (<peer-id>). Each side runs:
CHAT=~/Downloads/claude-chat.jsonl
if [[ -s "$CHAT" ]]; then
STAMP=$(date '+%Y-%m-%dT%H-%M-%S%z')
mv "$CHAT" "${CHAT}.${STAMP}.bak"
fi
touch "$CHAT"
Coordination on who rotates: the first session to invoke the skill should rotate; the second should NOT. Either coordinate verbally before both starting, OR have the second session check [[ -s "$CHAT" ]] first (skip rotation if the file already has content from the first session's announcement).
After rotating, append your arrival announcement (replace <my-id> / <peer-id>):
TS=$(date '+%FT%T%z')
printf '{"ts":"%s","from":"<my-id>","to":"<peer-id>","kind":"channel-open","msg":"<my-id>: on the channel. Monitor armed."}\n' "$TS" >> ~/Downloads/claude-chat.jsonl
Watch (arm the Monitor)
Each side arms the Monitor with a tail -F that filters out its own messages (so writes don't echo as events):
Monitor:
command: tail -F -n 0 ~/Downloads/claude-chat.jsonl | grep --line-buffered '"from":"<peer-id>"'
description: <peer-id> messages in ~/Downloads/claude-chat.jsonl
persistent: true
timeout_ms: 3600000
Each new line from <peer-id> becomes a <task-notification> that wakes this session. The -n 0 flag on tail means "start from end of file"; no replay of historical content.
The Monitor task ID returned (e.g. b3f6lf7o7) is what you'll pass to TaskStop at the end of the collab.
HARD RULE: log the operator's words to the file
Every message the operator (the human, U) sends to either Claude during a claude-chat collab MUST be appended to ~/Downloads/claude-chat.jsonl by whichever Claude received it, attributed to her by name. This is not optional and not a "nice to have"; the file is the canonical transcript of the collab and the operator's directives belong on it alongside c1/c2 chat. The peer Claude cannot do its job if it doesn't see what the operator just said to the other side.
Format:
TS=$(date '+%FT%T%z')
printf '{"ts":"%s","from":"the operator","to":"<c1-or-c2-or-c1,c2>","kind":"directive","msg":"the operator: <her words, paraphrased only if needed to fit in 4KB; otherwise verbatim>"}\n' "$TS" >> ~/Downloads/claude-chat.jsonl
When to log:
- Always, for every the operator message that affects the collab (scope, routing, priorities, technical direction, channel rules).
- Log BEFORE acting on her instruction, so the peer Claude sees the directive at the same time you do.
- Use
to:"c1,c2" if her message is jointly addressed; use to:"c2" (or c1) if she's only talking to one side.
kind:"directive" for instructions, kind:"question" for questions, kind:"feedback" for corrections, kind:"answer" if she's answering a question you asked her.
When NOT to log:
- Side-channel chitchat unrelated to the collab.
- Her input on completely separate tasks she's running in the same session.
- If unsure, log it. Over-logging is recoverable; missing a directive is not.
Do not paraphrase or summarize her directives unless they exceed 4 KB. Verbatim is the default; the peer Claude needs the exact words to interpret intent.
Send (append a c1/c2 message)
To send any message, run:
TS=$(date '+%FT%T%z')
printf '{"ts":"%s","from":"<my-id>","to":"<peer-id>","kind":"<topic>","msg":"<text>"}\n' "$TS" >> ~/Downloads/claude-chat.jsonl
Conventions for the kind field:
ping / pong: handshake / liveness check
channel-open / channel-close: lifecycle
scope-claim: "I'm taking on X, don't duplicate"
building: "I'm building it now" (lifecycle status; see emoji rule below)
pr-opened: "PR is up for critic" (lifecycle status; see emoji rule below)
handoff: "I'm done with X, you can pick up Y"
status: routine progress update
block: "I'm waiting on something / someone"
route-request: "the operator please route this"
directive / question / feedback / answer: reserved for from:"the operator" rows (see hard rule above)
<bug-or-pr-number>-<topic>: incident-scoped chatter (e.g. 779-fix-progress)
HARD RULE: emoji prefix for lifecycle action messages
Some channel messages mark a state-of-work transition that the peer Claude and the operator want to see at a glance. Prefix the msg field with the matching emoji so a quick scroll surfaces them visually. This is the one exception to the project-wide "no emoji unless explicitly requested" rule; the operator explicitly asked for it on 2026-05-19 because the channel was getting hard to skim.
Required emoji prefixes:
kind field | Emoji prefix in msg | When to send |
|---|
building | 🔨 | The moment you actually start coding / scripting on a scope you've already claimed. NOT at scope-claim time; only when fingers-on-keys. |
pr-opened | 🚀 | The moment a PR is up and ready for the peer Claude to critic. Include the PR URL in the msg. |
| (other action kinds) | (see below) | Optional but recommended. |
Recommended emoji prefixes (use when applicable, not required):
| Action | Emoji | Meaning |
|---|
critic-started | 🔍 | I'm reading the PR now |
critic-done | 🧐 | Critic posted on the PR |
tests-passing | ✅ | Local / CI tests green |
tests-failing | ❌ | Local / CI tests red |
fix-in-progress | 🛠️ | Working on the fix |
bug-found | 🐛 | New defect identified |
paused | ⏸️ | Stopped, waiting on something or someone |
unblocked | ▶️ | Resumed after a pause |
retrying | 🔄 | Re-running after a transient failure |
merged | 🎉 | PR landed |
reverted | ↩️ | PR reverted |
archived | 📦 | Closed / shelved |
What this does NOT cover: prose-style messages where emoji would be decorative noise (skip those). Emoji are functional signals, not flair. One emoji at the start of the msg, then the actual content.
Example:
TS=$(date '+%FT%T%z')
printf '{"ts":"%s","from":"c2","to":"c1","kind":"pr-opened","msg":"%s c2: PR #784 up for critic, branch docs/779-postmortem-template against develop. Three files. ETA on your read?"}\n' "$TS" "🚀" >> ~/Downloads/claude-chat.jsonl
(Use printf "%s" for the emoji argument so the shell handles the multi-byte character cleanly across bash 3.2 / 4 / 5.)
Send (append a c1/c2 message)
Keep msg under 4 KB to stay within POSIX atomic-append guarantee. If you need to send more, paste the long content elsewhere (PR description, repo doc, gist) and link to it from the msg.
Receive (handle a <task-notification>)
When the Monitor fires, you'll see a notification like:
<task-notification>
<task-id>b3f6lf7o7</task-id>
<summary>Monitor event: "<peer-id> messages in ~/Downloads/claude-chat.jsonl"</summary>
<event>{"ts":"...","from":"<peer-id>","to":"<my-id>","kind":"...","msg":"..."}</event>
</task-notification>
The full message JSON is in <event>. Parse it and reply by appending your own line (see "Send" above).
Stop (end the collab)
When the collab ends, on each side:
- Append a
channel-close message so the transcript records the end:
TS=$(date '+%FT%T%z')
printf '{"ts":"%s","from":"<my-id>","to":"<peer-id>","kind":"channel-close","msg":"<my-id>: closing. Reason: <text>."}\n' "$TS" >> ~/Downloads/claude-chat.jsonl
TaskStop the Monitor (task ID from setup).
- Leave the file in place. The
.bak archives + the current file together form the audit trail of this and previous collabs.
Adding a third process (rare)
The file format permits N senders. If a third Claude joins mid-collab as c3:
- c3 rotates the file? No, would lose in-flight context. c3 just appends to the live file.
- c1 and c2 should re-arm their Monitors to filter on
("from":"c2" OR "from":"c3") and ("from":"c1" OR "from":"c3") respectively. Easiest: drop the grep filter entirely and have each side filter out its own messages in code instead.
- Cost: more notifications, more parsing on each side.
In practice if you reach three Claudes on one channel, the right move is to split into pairs or hand routing to U.
Late joiner reads history
When a Claude joins an in-flight collab, before arming Monitor it can read the prior chat:
tail -20 ~/Downloads/claude-chat.jsonl | jq -r '"\(.ts) [\(.from)→\(.to)] \(.kind): \(.msg | .[:200])"'
Live transcript for the human (U)
In a separate terminal tab, run:
tail -f ~/Downloads/claude-chat.jsonl | jq -r '"\(.ts) [\(.from)→\(.to)] \(.kind): \(.msg)"'
This gives U a real-time, human-readable view of the chat without having to open the JSONL file directly.
Validated example: cupertino #779 postmortem collab (2026-05-19)
The first end-to-end use of this skill. c1 (save-log diagnostics, PRs #780 / #781 / #782) and c2 (postmortem doc + template, PR #784) initially coordinated via GH issue #783 with 30s + 240s polling. Migration to this skill cut handshake latency from minutes to seconds. Transcript preserved as ~/Downloads/claude-chat.jsonl.<timestamp>.bak.