0.6.0 removed the working-directory fallback, which was right, but it left every non-git working surface with nothing to derive from. The read path declines silently by design - the hook must never fail a session - so such a surface simply stops seeing its inbox: no error, no exit code, nothing. That is the same loss-looks-like-normal shape 0.6.0 set out to remove, reintroduced one layer up. The hook now forwards CLAUDE_COORD_REPO verbatim as --repo. This is not the fallback returning, and the distinction is the whole point: the fallback GUESSED a name from wherever the session happened to stand, while a declaration is written down in that directory's settings, readable back, and deletable. Identity stays a choice someone made. Forwarding as --repo rather than reimplementing anything means it inherits every engine rule, including that an explicit override never claims <repo>/.origin - otherwise a surface borrowing a name would steal the claim from the checkout that owns it. Both halves are pinned by tests, and the shared test helper now deletes CLAUDE_COORD_REPO unless a test asks for it, so a value set globally later cannot silently satisfy the tests that prove the hook resolves nothing on its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U6EixQo6hpoRCVtiAXdnFs
8.5 KiB
| name | description | version |
|---|---|---|
| coord-send | Send an inter-repo coordination message through the local coord mailbox — a natural-language front door over the `coord-send` script. Use this whenever the user wants to notify, tell, message, ping, or coordinate with another repository (or all repositories) about work happening in the current repo: "send info to all repos", "tell repo-x the bug is fixed", "let repo Y and Z know", "broadcast that the spec changed", "reply to that coord message", or a deferred/conditional notice like "when the build is green, notify repo X". Also triggers on Norwegian phrasings: "send info til alle repo", "varsle repo X", "gi beskjed til Y og Z", "kringkast at …", "svar på coord-meldingen", "når Z er ferdig, varsle X". Trigger even when the user names a repo plus something to convey without saying "coord" explicitly — routing a message to another repo IS this skill. Also covers retiring a broadcast that has become wrong or obsolete: "retract that broadcast", "that announcement is outdated, pull it", "trekk tilbake kringkastingen", "den broadcasten er utdatert". | 0.7.0 |
coord-send — natural-language front door for inter-repo messages
This skill turns a plain request ("tell repo X that Y happened") into the right
coord-send invocation. The script is the engine; this skill is only the mapping
from intent to arguments. It never edits mailbox files by hand — always go through
the script, because the script owns the filename grammar, frontmatter, and delivery
guarantees.
Boundary — why messages are thin
The mailbox is transport, not state. A coord message is a notice, not the source of truth. The durable record of any decision lives in the owning repo's state files / docs and its git history. So when you compose a body, write a self-contained heads-up and point at where the real record lives (a commit hash, a doc path) — don't try to make the message itself the canonical artifact. Coordination content is also private: it never belongs on a public surface.
The engine
Resolve the script path portably — this one expression is correct both when the
skill runs bundled inside the plugin and when the scripts are installed as
personal scripts under ~/.claude/scripts/:
CSEND="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude}/scripts/coord-send.sh"
Interface (body comes from a quoted heredoc so nothing in it is shell-expanded):
# to one repo
"$CSEND" --to <repo> --subject "<subject>" <<'BODY'
<message body>
BODY
# to every repo (accumulates; first-time newcomers see it too)
"$CSEND" --broadcast --subject "<subject>" <<'BODY'
<message body>
BODY
# reply to a message this repo received (routes to the original sender and
# marks the original handled; target + "Re: …" subject are inferred)
"$CSEND" --reply-to <message-filename> <<'BODY'
<message body>
BODY
# retire one of THIS repo's own broadcasts (no subject, no body)
"$CSEND" --retract <broadcast-filename>
Sender identity (--from) defaults to the basename of the current git toplevel, so
you almost never set it. Outside a git repo there is no default — the send refuses
with exit 2 rather than naming itself after the working directory, so on a global
surface (~/repos, $HOME) pass --from <repo> and make the identity a choice.
Exit 0 = delivered; exit 2 = usage/IO error (read stderr and fix the arguments
rather than retrying blindly).
Choosing the target
-
One named repo →
--to <repo>. The repo name is its directory basename; use the name the user gave. No registration exists — sending to a new name just creates that repo's inbox, so a typo silently creates a dead mailbox. If unsure a name is real, check~/repos/or existing~/.claude/coord/<repo>/before sending. -
Several named repos ("X and Y") → loop
--toonce per repo with the same subject and body. There is no multi-target flag; the loop is the mechanism:for repo in repo-x repo-y; do "$CSEND" --to "$repo" --subject "<subject>" <<'BODY' <body> BODY done -
All repos →
--broadcast. Prefer this only when the notice genuinely concerns everyone, because broadcasts accumulate and every future first-time repo sees the standing backlog. For a bounded, known set, loop--toinstead so unrelated future repos don't inherit it. A broadcast that later turns out wrong can be retired with--retract(see below), but only for repos that have not received it yet. -
A reply to something received →
--reply-to <filename>. Use the filename from the injected inbox/archive; it resolves the sender and closes the original in one step.
Retracting a broadcast
When a broadcast has become wrong or obsolete, retire it — don't send a correction
and leave the original standing, because every future first-time repo would receive
both. --retract <broadcast-filename> archives it out of the delivery queue.
Three things to be honest about when you report it:
- It is un-send, not recall. Repos that already received the broadcast keep it. Retraction only stops delivery to repos that haven't seen it yet. If the old news actively misleads someone who already got it, a correcting broadcast is still needed — retraction alone does not reach them.
- Only the sender may retract. The
from:field must match this repo's identity. If the announcing repo has since been renamed, its old identity no longer resolves, so pass--from <old-sender>explicitly (read the sender off the file). Say that you did. - Nothing is deleted. The message moves to
_broadcast/archive/. Never remove a mailbox file by hand — the script owns the filename grammar and delivery guarantees.
Find the filename in ~/.claude/coord/_broadcast/inbox/ (or from the injected
--- broadcast: <filename> --- line) rather than guessing it.
Composing the message
Derive a short, specific --subject from the intent if the user didn't give one
("ingest bug fixed", not "update"). In the body, state what happened, what the
recipient should know or do, and a pointer to the durable record (commit hash, doc
path). Keep it to the point — this is a notice, not a report. Match the repo's
language convention for message content; keep it sober and factual.
Deferred and conditional sends
"When Z is done, notify X" means: do not send now. Hold the intent — the target, subject, and body — and continue the work. The moment condition Z is satisfied in this session, fire the send. This works because you carry the intent across turns within a session.
Be honest about the limit: a skill cannot persist intent across sessions. If the session is ending and the condition still hasn't been met, surface the pending message to the user so it isn't silently lost — don't imply it will fire later on its own.
After sending
Report what actually happened: the target(s), the subject, and the delivered filename(s) from the script's stdout. If a send failed (exit 2), say so with the stderr reason rather than claiming success. Delivery reaches the recipient at their next session start (the receive side is a hook), so tell the user it's queued, not that the other repo has seen it.
Examples
Example 1 — single repo, composed body
Input: "tell repo-x the ingest bug is fixed, it was commit abc1234"
Action: --to repo-x --subject "ingest bug fixed", body naming the fix and
pointing at commit abc1234 and the owning doc.
Example 2 — broadcast
Input: "let all the repos know the shared spec grew a new validation rule"
Action: --broadcast --subject "spec: new validation rule", body summarizing
the change and pointing at the spec's owning repo.
Example 3 — bounded multi-target
Input: "gi beskjed til repo-x og repo-y om at gaten er re-pinnet"
Action: loop --to repo-x then --to repo-y, same subject "shared gate re-pinned".
Example 4 — deferred
Input: "når testene er grønne, varsle repo-y"
Action: keep working; once the test run passes this session, send
--to repo-y with the green result. If the session ends first, surface the
pending notice to the user.
Example 5 — reply
Input: "svar på coord-meldingen fra repo-x at vi tar det"
Action: --reply-to <that-message-filename>, body acknowledging and stating the plan.
Example 6 — retract
Input: "den gamle kringkastingen om plugin-navnet er utdatert, trekk den"
Action: locate the file in _broadcast/inbox/, then --retract <that-filename>
(adding --from <old-sender> if this repo has been renamed since). Report that
repos which already received it are unaffected.