feat(engine): retire a broadcast with coord-send --retract
Nothing could remove a message from _broadcast/inbox/. coord-done is directed-only and never touches the broadcast queue, so the backlog could only grow: every new repo received the entire standing history at its first session, including announcements that had since become false. --retract <filename> archives the message into _broadcast/archive/, so no future repo is served it. Three deliberate limits, all pinned by tests: - Un-send, not recall. Repos that already received it keep it; _broadcast/seen/ is delivery history and is left untouched. - Only the sender may retract (from: must match the repo identity). --from overrides it, as everywhere else in the engine, which makes the check an accident guard rather than a security boundary. - Nothing is deleted, mirroring coord-done. Retracting twice is a no-op. The branch runs before every send-side validation and before the stdin body read, since a retract carries no subject and no body. Selftest 70 -> 82 (new section 19). Also fixes two README defects the feature exposed: the install command still named coord@ after the v0.3.0 rename, and the docs advised pruning _broadcast/inbox/ by hand, which contradicted the rule that the script owns mailbox files.
This commit is contained in:
parent
c27b20fc62
commit
316b8acdd2
8 changed files with 184 additions and 15 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "repo-mailbox",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"description": "Local mailbox for coordination between Claude Code sessions in different repositories. Directed messages and broadcasts as plain Markdown files on your own disk, injected as context at session start. Local, private, no network.",
|
||||
"author": {
|
||||
"name": "Kjell Tore Guttormsen"
|
||||
|
|
|
|||
34
CHANGELOG.md
34
CHANGELOG.md
|
|
@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.4.0] - 2026-07-25
|
||||
|
||||
### Added
|
||||
|
||||
- **`coord-send --retract <filename>`: a broadcast can finally be retired.**
|
||||
Until now nothing could remove a message from `_broadcast/inbox/`:
|
||||
`coord-done` is directed-only and never touches the broadcast queue. The
|
||||
backlog could therefore only grow, and every *new* repo received the entire
|
||||
standing history at its first session — including announcements that had
|
||||
since become false. Retract moves the message to `_broadcast/archive/`, so
|
||||
no future repo is served it.
|
||||
|
||||
Deliberate limits, both documented and pinned by tests:
|
||||
|
||||
- **Un-send, not recall.** Repos that already received the broadcast keep
|
||||
it; `_broadcast/seen/` is delivery history and is left untouched.
|
||||
- **Only the sender may retract** (`from:` must match the repo identity).
|
||||
`--from` overrides this, as it does everywhere else in the engine, which
|
||||
makes the check an accident guard rather than a security boundary.
|
||||
- **Nothing is deleted**, mirroring `coord-done`. Retracting twice is a
|
||||
no-op (exit 0).
|
||||
|
||||
- Selftest grew from 70 to 82 checks (new section 19 covers retraction:
|
||||
authorization, archiving, non-delivery to new repos, no collateral damage to
|
||||
other broadcasts, invalid names, flag combinations, and idempotency).
|
||||
|
||||
### Fixed
|
||||
|
||||
- README's install command still said `claude plugin install
|
||||
coord@ktg-plugin-marketplace` after the v0.3.0 rename.
|
||||
- README told the reader to prune `_broadcast/inbox/` by hand when a notice
|
||||
stopped being relevant — advice that contradicted the rule that the script
|
||||
owns mailbox files. `--retract` is now the supported path.
|
||||
|
||||
## [0.3.0] - 2026-07-25
|
||||
|
||||
### Changed
|
||||
|
|
|
|||
17
CLAUDE.md
17
CLAUDE.md
|
|
@ -14,7 +14,7 @@ marketplace plugin. Three components, one boundary:
|
|||
grammar, frontmatter, delivery, archiving, the seen set. `coord-send.sh`
|
||||
writes, `coord-inbox.sh` reads (formatted for context injection),
|
||||
`coord-done.sh` archives. Everything is pinned by `coord-selftest.sh`
|
||||
(70 checks, throwaway mailbox via `CLAUDE_COORD_DIR`).
|
||||
(82 checks, throwaway mailbox via `CLAUDE_COORD_DIR`).
|
||||
- **Hook (`hooks/scripts/session-start.mjs`):** thin zero-dependency Node
|
||||
wrapper (marketplace convention: hooks are `.mjs`) that calls
|
||||
`coord-inbox.sh` and emits the `hookSpecificOutput.additionalContext`
|
||||
|
|
@ -35,7 +35,7 @@ and frames it; the send side sanitizes line-oriented fields.
|
|||
- Zero dependencies everywhere: bash + coreutils in the engine, `node:`
|
||||
builtins only in hook and tests.
|
||||
- TDD: no behavior change without a failing selftest check first.
|
||||
`bash scripts/coord-selftest.sh` must exit 0 (70/70).
|
||||
`bash scripts/coord-selftest.sh` must exit 0 (82/82).
|
||||
- English for all code, docs, and commit messages (public repo). Norwegian
|
||||
trigger aliases in the skill description are deliberate.
|
||||
- Conventional Commits: `type(scope): description`.
|
||||
|
|
@ -66,6 +66,13 @@ uncommitted. When that happens, commit the catalog's `marketplace.json` +
|
|||
|
||||
Empty — the post-v0.1.0 queue (atomic delivery, `.`/`..` rejection,
|
||||
selftest gaps, uniform `-h`) shipped in v0.2.0; broadcast self-delivery
|
||||
shipped in v0.2.1. `coord-inbox.sh` still ignores unknown arguments by
|
||||
design (hook context must never fail) but now warns about each one on
|
||||
stderr, which the hook discards.
|
||||
shipped in v0.2.1; broadcast retraction (`coord-send --retract`) shipped in
|
||||
v0.4.0, closing the last monotonically-growing surface. `coord-inbox.sh`
|
||||
still ignores unknown arguments by design (hook context must never fail)
|
||||
but now warns about each one on stderr, which the hook discards.
|
||||
|
||||
Two retraction limits are deliberate, not gaps: it is un-send and never
|
||||
recall (a repo that already received a broadcast keeps it — the seen set is
|
||||
delivery history and is left untouched), and the sender check is an accident
|
||||
guard, not a security boundary, because `--from` redefines identity here as
|
||||
it does everywhere else in the engine.
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
*AI-generated: all code produced by Claude Code through dialog-driven development.*
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
|
@ -31,6 +31,7 @@ Mailbox layout (default `~/.claude/coord/`, override with `CLAUDE_COORD_DIR`):
|
|||
<repo>/inbox/ pending directed messages TO <repo>
|
||||
<repo>/archive/ handled messages (kept, never deleted)
|
||||
_broadcast/inbox/ messages to ALL repos (accumulate)
|
||||
_broadcast/archive/ retracted broadcasts (kept, never deleted)
|
||||
_broadcast/seen/<repo> per-repo seen set: delivered broadcast filenames, one per line
|
||||
|
||||
Repo identity is the basename of the git toplevel (fallback: the working directory). There is no registration — a repo joins the moment something is sent to it, or when it first reads a broadcast.
|
||||
|
|
@ -45,12 +46,14 @@ Message format (filename `<UTC-timestamp>-<uniq>-from-<sender>.md`):
|
|||
---
|
||||
<body>
|
||||
|
||||
**Lifecycle — deliver until done.** Directed messages are NOT archived on read. They stay pending and are re-injected at every session start (startup, `/clear`, resume) until explicitly marked handled: replying (`--reply-to`) archives the original, or `coord-done <file>` archives it without a reply. `/clear` never loses a message. Broadcasts are delivered once per repo via the seen set, and never back to their own sender — the announcing repo's seen entry is written at delivery time, so it is not told its own news. Broadcasts otherwise accumulate; prune `_broadcast/inbox/` manually when a notice stops being relevant to future first-time repos.
|
||||
**Lifecycle — deliver until done.** Directed messages are NOT archived on read. They stay pending and are re-injected at every session start (startup, `/clear`, resume) until explicitly marked handled: replying (`--reply-to`) archives the original, or `coord-done <file>` archives it without a reply. `/clear` never loses a message. Broadcasts are delivered once per repo via the seen set, and never back to their own sender — the announcing repo's seen entry is written at delivery time, so it is not told its own news.
|
||||
|
||||
**Retracting a broadcast.** Broadcasts otherwise accumulate, and every future first-time repo receives the whole standing backlog — including announcements that have since become false. `coord-send --retract <filename>` retires one: it moves the message out of `_broadcast/inbox/` into `_broadcast/archive/`, so no future repo is served it. This is un-send, **not recall** — repos that already received it are unaffected. Only the original sender may retract (`from:` must match your repo identity); `--from <sender>` overrides that, which makes it an accident guard rather than a security boundary. Retracting twice is a no-op.
|
||||
|
||||
## Install
|
||||
|
||||
claude plugin marketplace add https://git.fromaitochitta.com/open/ktg-plugin-marketplace.git
|
||||
claude plugin install coord@ktg-plugin-marketplace
|
||||
claude plugin install repo-mailbox@ktg-plugin-marketplace
|
||||
|
||||
The plugin ships empty: your mailbox is created lazily on first send, on your machine, and stays there.
|
||||
|
||||
|
|
@ -65,6 +68,7 @@ The plugin ships empty: your mailbox is created lazily on first send, on your ma
|
|||
coord-send.sh --to <repo> --subject "<subject>" [--message "<text>"] # or body on stdin
|
||||
coord-send.sh --broadcast --subject "<subject>" <<'BODY' ... BODY
|
||||
coord-send.sh --reply-to <filename> [--subject "Re: ..."] # routes + closes original
|
||||
coord-send.sh --retract <filename> [--from <sender>] # retire your own broadcast
|
||||
coord-inbox.sh [--repo <name>] # print pending (what the hook injects)
|
||||
coord-done.sh <filename>... | --all # archive without replying
|
||||
|
||||
|
|
@ -81,7 +85,7 @@ Cross-repo message content is untrusted input by design:
|
|||
|
||||
- **Atomic delivery:** the temp file is created inside the destination directory (dot-prefixed, invisible to the inbox glob), so the final rename never crosses filesystems and readers never observe a half-written message.
|
||||
|
||||
Every guarantee above is pinned by the 70-check selftest, including forgery-resistance regressions.
|
||||
Every guarantee above is pinned by the 82-check selftest, including forgery-resistance regressions.
|
||||
|
||||
## The Six Rules
|
||||
|
||||
|
|
@ -100,7 +104,7 @@ Every guarantee above is pinned by the 70-check selftest, including forgery-resi
|
|||
|
||||
## Development
|
||||
|
||||
bash scripts/coord-selftest.sh # 70 checks against a throwaway mailbox
|
||||
bash scripts/coord-selftest.sh # 82 checks against a throwaway mailbox
|
||||
npm test # same selftest via node --test
|
||||
|
||||
TDD is the house rule: every behavior change lands with a failing selftest check first.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "repo-mailbox",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
|
|
|
|||
|
|
@ -235,6 +235,49 @@ printf '%s' "$aerr" | grep -q 'unknown argument: --bogus-flag'; check "inbox: un
|
|||
aout="$("$INBOX" --repo argrepo --bogus-flag 2>/dev/null)"; rc=$?
|
||||
[ "$rc" -eq 0 ] && printf '%s' "$aout" | grep -q 'ARG-BODY'; check "inbox: unknown argument still reads the inbox and exits 0" $?
|
||||
|
||||
# 19. A broadcast can be retired. Without this, the broadcast backlog grows
|
||||
# monotonically: every NEW repo receives the whole history at its first session,
|
||||
# including announcements that have since become false. Retract is sender-side
|
||||
# ("un-send"), archives rather than deletes, and is NOT recall - repos that
|
||||
# already received the message keep it. Every invocation reads from /dev/null so
|
||||
# a regression that falls through to the stdin body read fails instead of hanging.
|
||||
"$SEND" --broadcast --from retractor --subject "oops" --message "RETRACT-ME-BODY" >/dev/null
|
||||
rbase="$(basename "$(ls "$CLAUDE_COORD_DIR"/_broadcast/inbox/*-from-retractor.md 2>/dev/null | head -1)")"
|
||||
"$SEND" --broadcast --from keeper --subject "keep" --message "KEEP-BC-BODY" >/dev/null
|
||||
nr="$("$SEND" --retract "$rbase" --from someone-else </dev/null 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 2 ] && printf '%s' "$nr" | grep -q 'sent by retractor'; check "retract by a non-sender is refused" $?
|
||||
[ -e "$CLAUDE_COORD_DIR/_broadcast/inbox/$rbase" ]; check "refused retract leaves the broadcast pending" $?
|
||||
vo="$("$SEND" --retract </dev/null 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 2 ] && printf '%s' "$vo" | grep -q 'requires a value'; check "retract: missing value is a usage error" $?
|
||||
uo="$("$SEND" --retract no-such-broadcast.md --from retractor </dev/null 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 2 ] && printf '%s' "$uo" | grep -q 'not found'; check "retract of an unknown broadcast exits 2" $?
|
||||
bad_rc=0
|
||||
for bad in . .. sub/dir; do
|
||||
bo="$("$SEND" --retract "$bad" --from retractor </dev/null 2>&1)"; brc=$?
|
||||
{ [ "$brc" -eq 2 ] && printf '%s' "$bo" | grep -q 'invalid --retract'; } || bad_rc=1
|
||||
done
|
||||
[ "$bad_rc" -eq 0 ]; check "retract rejects . / .. / path names as invalid" $?
|
||||
comb_rc=0
|
||||
for combo in --broadcast "--to:x" "--reply-to:y"; do
|
||||
cflag="${combo%%:*}"; cval="${combo#*:}"
|
||||
if [ "$cval" = "$combo" ]; then
|
||||
co="$("$SEND" --retract "$rbase" "$cflag" --from retractor </dev/null 2>&1)"; crc=$?
|
||||
else
|
||||
co="$("$SEND" --retract "$rbase" "$cflag" "$cval" --from retractor </dev/null 2>&1)"; crc=$?
|
||||
fi
|
||||
{ [ "$crc" -eq 2 ] && printf '%s' "$co" | grep -q 'cannot be combined'; } || comb_rc=1
|
||||
done
|
||||
[ "$comb_rc" -eq 0 ]; check "retract cannot be combined with --to/--broadcast/--reply-to" $?
|
||||
ro="$("$SEND" --retract "$rbase" --from retractor </dev/null 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 0 ]; check "retract by the sender exits 0" $?
|
||||
[ ! -e "$CLAUDE_COORD_DIR/_broadcast/inbox/$rbase" ]; check "retracted broadcast leaves the broadcast inbox" $?
|
||||
[ -e "$CLAUDE_COORD_DIR/_broadcast/archive/$rbase" ]; check "retracted broadcast is archived, never deleted" $?
|
||||
fout="$("$INBOX" --repo fresh-reader)"
|
||||
[ "$(printf '%s' "$fout" | grep -c 'RETRACT-ME-BODY')" -eq 0 ]; check "retracted broadcast is never delivered to a new repo" $?
|
||||
printf '%s' "$fout" | grep -q 'KEEP-BC-BODY'; check "unretracted broadcasts still reach that repo" $?
|
||||
io="$("$SEND" --retract "$rbase" --from retractor </dev/null 2>&1)"; rc=$?
|
||||
[ "$rc" -eq 0 ] && printf '%s' "$io" | grep -q 'already retracted'; check "re-retracting an already retracted broadcast is a no-op (exit 0)" $?
|
||||
|
||||
echo "----"
|
||||
echo "PASS=$PASS FAIL=$FAIL"
|
||||
[ "$FAIL" -eq 0 ]
|
||||
|
|
|
|||
|
|
@ -6,9 +6,13 @@
|
|||
# coord-send.sh --to <repo> --subject "<subject>" [--from <repo>] [--message "<text>"]
|
||||
# coord-send.sh --broadcast --subject "<subject>" [--from <repo>] [--message "<text>"]
|
||||
# coord-send.sh --reply-to <file> [--subject "Re: ..."] [--from <repo>] [--message "<text>"]
|
||||
# coord-send.sh --retract <file> [--from <repo>]
|
||||
# Body comes from --message, or from stdin (heredoc) when --message is omitted.
|
||||
# --reply-to <basename> replies to a message in THIS repo's inbox/archive: it
|
||||
# routes to the original sender and marks the original handled (coord-done).
|
||||
# --retract <basename> retires one of YOUR OWN broadcasts: it is archived out of
|
||||
# the broadcast queue so no future repo receives it. This is un-send, not
|
||||
# recall - repos that already received it are unaffected.
|
||||
# --from overrides the sender/self identity (default: basename of git toplevel/cwd).
|
||||
#
|
||||
# Exit: 0 delivered, 2 usage/IO error. ASCII only, bash 3.2 safe.
|
||||
|
|
@ -18,6 +22,7 @@ export LC_ALL=C
|
|||
COORD="${CLAUDE_COORD_DIR:-$HOME/.claude/coord}"
|
||||
|
||||
TO=""; BROADCAST=0; SUBJECT=""; FROM=""; MESSAGE=""; HAVE_MESSAGE=0; REPLYTO=""; REPLY_ORIG=""
|
||||
RETRACT=""
|
||||
|
||||
# bash 3.2: `shift 2` past the end of $# is a no-op, so a trailing value-flag
|
||||
# without its value would loop forever. Every two-arg flag must check first.
|
||||
|
|
@ -30,6 +35,7 @@ while [ $# -gt 0 ]; do
|
|||
--to) require_value --to $#; TO="$2"; shift 2 ;;
|
||||
--broadcast) BROADCAST=1; shift ;;
|
||||
--reply-to) require_value --reply-to $#; REPLYTO="$2"; shift 2 ;;
|
||||
--retract) require_value --retract $#; RETRACT="$2"; shift 2 ;;
|
||||
--subject) require_value --subject $#; SUBJECT="$2"; shift 2 ;;
|
||||
--from) require_value --from $#; FROM="$2"; shift 2 ;;
|
||||
--message) require_value --message $#; MESSAGE="$2"; HAVE_MESSAGE=1; shift 2 ;;
|
||||
|
|
@ -52,6 +58,46 @@ fi
|
|||
sanitize_field() { printf '%s' "$1" | tr '\r\n' ' ' | tr -d '\000-\037'; }
|
||||
FROM="$(sanitize_field "$FROM")"
|
||||
|
||||
# --- Retract mode: retire one of our own broadcasts ---
|
||||
# A broadcast has no per-repo owner that can close it: coord-done is directed-only
|
||||
# and never touches _broadcast/. Without this branch a stale announcement stays in
|
||||
# the queue and is delivered to every FUTURE repo forever, so the backlog can only
|
||||
# grow. Runs before every send-side validation and before the stdin body read: a
|
||||
# retract carries no subject and no body.
|
||||
# The sender check is an accident guard, not a security boundary - --from
|
||||
# redefines identity here exactly as it does everywhere else in this script. It
|
||||
# exists so a wrong filename cannot silently retire another repo's announcement.
|
||||
if [ -n "$RETRACT" ]; then
|
||||
if [ "$BROADCAST" -eq 1 ] || [ -n "$TO" ] || [ -n "$REPLYTO" ]; then
|
||||
echo "coord-send: --retract cannot be combined with --to/--broadcast/--reply-to" >&2; exit 2
|
||||
fi
|
||||
case "$RETRACT" in */*|.|..|"") echo "coord-send: invalid --retract name: $RETRACT" >&2; exit 2 ;; esac
|
||||
BC_INBOX="$COORD/_broadcast/inbox"
|
||||
BC_ARCHIVE="$COORD/_broadcast/archive"
|
||||
if [ ! -e "$BC_INBOX/$RETRACT" ]; then
|
||||
# Idempotent like coord-done: retracting twice is a no-op, not an error.
|
||||
if [ -e "$BC_ARCHIVE/$RETRACT" ]; then
|
||||
echo "coord-send: broadcast already retracted ($RETRACT)"; exit 0
|
||||
fi
|
||||
echo "coord-send: --retract broadcast not found: $RETRACT" >&2; exit 2
|
||||
fi
|
||||
ORIG_FROM="$(grep -m1 '^from:' "$BC_INBOX/$RETRACT" 2>/dev/null | sed 's/^from:[[:space:]]*//')"
|
||||
if [ "$ORIG_FROM" != "$FROM" ]; then
|
||||
echo "coord-send: refusing to retract a broadcast sent by ${ORIG_FROM:-unknown} (you are $FROM); pass --from ${ORIG_FROM:-<sender>} if that is really you" >&2
|
||||
exit 2
|
||||
fi
|
||||
# Archived, never deleted (coord-done's rule), and the move stays inside
|
||||
# _broadcast/ so it is a same-filesystem rename: no half-retracted state.
|
||||
mkdir -p "$BC_ARCHIVE" 2>/dev/null || { echo "coord-send: cannot create $BC_ARCHIVE" >&2; exit 2; }
|
||||
if ! mv "$BC_INBOX/$RETRACT" "$BC_ARCHIVE/$RETRACT" 2>/dev/null; then
|
||||
echo "coord-send: retract failed for $RETRACT" >&2; exit 2
|
||||
fi
|
||||
# _broadcast/seen/* is deliberately left alone: those entries are delivery
|
||||
# history, and one naming an archived file is inert.
|
||||
echo "coord-send: retracted broadcast ($RETRACT); repos that already received it are unaffected"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Reply mode: resolve target + default subject from the original message ---
|
||||
if [ -n "$REPLYTO" ]; then
|
||||
if [ "$BROADCAST" -eq 1 ] || [ -n "$TO" ]; then
|
||||
|
|
|
|||
|
|
@ -11,8 +11,11 @@ description: >-
|
|||
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.
|
||||
version: "0.3.0"
|
||||
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".
|
||||
version: "0.4.0"
|
||||
---
|
||||
|
||||
# coord-send — natural-language front door for inter-repo messages
|
||||
|
|
@ -58,6 +61,9 @@ Interface (body comes from a quoted heredoc so nothing in it is shell-expanded):
|
|||
<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. Exit 0 = delivered; exit 2 = usage/IO error (read stderr and
|
||||
fix the arguments rather than retrying blindly).
|
||||
|
|
@ -80,11 +86,34 @@ fix the arguments rather than retrying blindly).
|
|||
- **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 `--to` instead so unrelated future
|
||||
repos don't inherit it.
|
||||
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
|
||||
|
|
@ -138,3 +167,9 @@ 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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue