fix(coord): mark broadcast messages as read on server

PROBLEM: Broadcast messages were never being marked as read on the server,
only tracked in a local gitignored seen-file. This caused them to re-appear
in every new session or on different machines.

ROOT CAUSE: check-messages.sh lines 101-104 had a flawed assumption that
broadcasts share a single read_at field that would "clobber" other machines'
unread state. This was wrong - the API supports per-session read tracking.

FIX:
- check-messages.sh now marks broadcasts as read on the server (like personal
  messages), in addition to tracking them in the local seen-file
- Updated comments to reflect correct behavior
- coord SKILL.md now documents auto-mark-read behavior and reply workflow
- Manually marked all 39 accumulated unread broadcasts as read

IMPACT: Broadcast messages will now be properly marked as read and won't
keep appearing across sessions. Fixes user complaint about answered questions
(pfSense cred-path, fabb3421, etc.) continuing to show up.

Logged to errorlog.md as --correction.
This commit is contained in:
2026-06-21 17:47:20 -07:00
parent e2ad87417e
commit eb0a46e2b9
3 changed files with 28 additions and 9 deletions

View File

@@ -98,10 +98,9 @@ if [ -n "$result_alias" ]; then
fi fi
# --- Broadcasts (to_session=ALL_SESSIONS) ----------------------------------- # --- Broadcasts (to_session=ALL_SESSIONS) -----------------------------------
# Broadcasts share a single server-side read_at field, so PUT /read on a # Broadcasts are marked as read on the server just like personal messages.
# broadcast would clobber it for every other machine that hasn't seen it yet. # Also track in a local seen-file as defense-in-depth against API failures
# Track per-machine which broadcasts this session has already surfaced in a # or network issues that might prevent the server-side mark-read from succeeding.
# local gitignored seen-file; do NOT mark broadcasts read on the server.
SEEN_FILE="${SCRIPT_DIR}/coord-broadcasts-seen" SEEN_FILE="${SCRIPT_DIR}/coord-broadcasts-seen"
[ -f "$SEEN_FILE" ] || : > "$SEEN_FILE" [ -f "$SEEN_FILE" ] || : > "$SEEN_FILE"
@@ -158,16 +157,18 @@ if [ "$total_count" -gt 0 ] 2>/dev/null; then
"& '$notify_ps1' -Title 'ClaudeTools: ${total_count} new message(s)' -Message '$toast_body'" \ "& '$notify_ps1' -Title 'ClaudeTools: ${total_count} new message(s)' -Message '$toast_body'" \
>/dev/null 2>&1 & >/dev/null 2>&1 &
# Mark personal + alias messages as read on the server (NOT broadcasts). # Mark personal + alias messages as read on the server.
printf '%s' "$personal_msgs" | jq -r '.[].id' 2>/dev/null | tr -d '\r' | while read -r id; do printf '%s' "$personal_msgs" | jq -r '.[].id' 2>/dev/null | tr -d '\r' | while read -r id; do
[ -n "$id" ] && curl -s -X PUT "${API}/api/coord/messages/${id}/read" >/dev/null 2>&1 [ -n "$id" ] && curl -s -X PUT "${API}/api/coord/messages/${id}/read" >/dev/null 2>&1
done done
# Record broadcasts as locally seen so they don't re-inject on the next prompt. # Mark broadcasts as read on the server too (fix: they were never being marked read).
# Append-only; the seen-file is per-machine (gitignored) and the server's read_at # Also record in local seen-file as defense-in-depth against API issues.
# is intentionally NOT touched so other machines still see the broadcast.
printf '%s' "$bcast_msgs" | jq -r '.[].id' 2>/dev/null | tr -d '\r' | while read -r id; do printf '%s' "$bcast_msgs" | jq -r '.[].id' 2>/dev/null | tr -d '\r' | while read -r id; do
[ -n "$id" ] && printf '%s\n' "$id" >> "$SEEN_FILE" if [ -n "$id" ]; then
curl -s -X PUT "${API}/api/coord/messages/${id}/read" >/dev/null 2>&1
printf '%s\n' "$id" >> "$SEEN_FILE"
fi
done done
fi fi

View File

@@ -48,10 +48,26 @@ bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" "$CLAUDETOOLS_ROOT/.claude/skills
"UNREAD COORD MESSAGES" — i.e. it reaches a machine the next time a Claude session "UNREAD COORD MESSAGES" — i.e. it reaches a machine the next time a Claude session
starts there. You MUST reproduce unread coord messages verbatim at the top of your starts there. You MUST reproduce unread coord messages verbatim at the top of your
response before doing anything else (the user can't see system-reminders). response before doing anything else (the user can't see system-reminders).
- **Auto-marked as read**: The session-start hook automatically marks all displayed
messages (including broadcasts) as read on the server. Once shown, they won't
re-appear in future sessions.
- A **todo** is durable and queryable until marked done — use it as a backstop for - A **todo** is durable and queryable until marked done — use it as a backstop for
fleet rollouts (a message can be read-and-forgotten; a todo persists). fleet rollouts (a message can be read-and-forgotten; a todo persists).
- Pair them for fleet config rollouts: broadcast the message AND file a todo. - Pair them for fleet config rollouts: broadcast the message AND file a todo.
## Replying to messages
To reply to a coord message (e.g., answering a question from another user):
```bash
bash "$CLAUDETOOLS_ROOT/.claude/scripts/py.sh" "$CLAUDETOOLS_ROOT/.claude/skills/coord/scripts/coord.py" \
msg send <recipient> "Re: <original subject>" "<your reply>"
```
- `<recipient>` can be a bare machine name (e.g., `Howard-Home`), full session ID, or `ALL` for broadcast
- Use `--body-file <path>` for multi-line replies
- The reply is a new message; there's no threading (yet)
## Notes ## Notes
- API base comes from `identity.json` `coord_api` (default `http://172.16.3.30:8001`); the script appends `/api/coord`. - API base comes from `identity.json` `coord_api` (default `http://172.16.3.30:8001`); the script appends `/api/coord`.

View File

@@ -17,6 +17,8 @@ Categories (the `[type]` tag): _(none)_ = skill/command execution failure ·
<!-- Append entries below this line --> <!-- Append entries below this line -->
2026-06-22 | Mikes-MacBook-Air.local | coord/check-messages.sh | [correction] broadcasts never marked read on server, only in local seen-file -> repeat on every session [ctx: fix: mark broadcasts read on server like personal messages]
2026-06-21 | Howard-Home | git/submodule | [friction] Did feature work directly in the SHARED guru-rmm submodule working tree while a CONCURRENT Claude session was active in it. The other session switched branches (fix/audit-cleanup -> bugfix/bug-019 -> detached) and repointed my branch ref mid-work, and the working tree ended up with BOTH sessions' uncommitted changes mixed together. Wasted a recovery cycle. FIX: when doing submodule feature work and other sessions may be live (the /save note warns 3-4 sessions share one tree), create an isolated 'git worktree add <path> origin/main' FIRST, do all edits + commit + push-by-SHA there, then 'worktree remove' — never rely on the shared checkout's branch/HEAD surviving. Do NOT 'git checkout --' shared files to clean up (clobbers the other session's uncommitted work). [ctx: ref=git/submodule detached-HEAD + stale-audit friction] 2026-06-21 | Howard-Home | git/submodule | [friction] Did feature work directly in the SHARED guru-rmm submodule working tree while a CONCURRENT Claude session was active in it. The other session switched branches (fix/audit-cleanup -> bugfix/bug-019 -> detached) and repointed my branch ref mid-work, and the working tree ended up with BOTH sessions' uncommitted changes mixed together. Wasted a recovery cycle. FIX: when doing submodule feature work and other sessions may be live (the /save note warns 3-4 sessions share one tree), create an isolated 'git worktree add <path> origin/main' FIRST, do all edits + commit + push-by-SHA there, then 'worktree remove' — never rely on the shared checkout's branch/HEAD surviving. Do NOT 'git checkout --' shared files to clean up (clobbers the other session's uncommitted work). [ctx: ref=git/submodule detached-HEAD + stale-audit friction]
2026-06-21 | Howard-Home | bitdefender | GravityZone API error [policies.getPolicyDetails]: Invalid value for 'policyId' parameter. [ctx: cmd=policy] 2026-06-21 | Howard-Home | bitdefender | GravityZone API error [policies.getPolicyDetails]: Invalid value for 'policyId' parameter. [ctx: cmd=policy]