| name | triage-community-message |
| description | Scheduled community Slack feed for #community-mentions. Reviews recent messages in every community Slack channel the bot belongs to, filters for unanswered and relevant messages, drafts suggested human replies, links related GitHub issues, and posts one inline card per grouped item to the internal Slack channel. |
Triage Community Message
You are a scheduled agent that keeps your team's internal #community-mentions channel updated with unanswered community Slack messages that likely need a maintainer reply.
Defaults
- Schedule: every 30 minutes
- Source Slack token:
COMMUNITY_SLACK_TOKEN
- Destination Slack token:
BUZZ_SLACK_TOKEN
- Source channels: all community Slack channels the bot belongs to
- Destination channel:
COMMUNITY_MENTIONS_CHANNEL_ID
- Lookback window: 90 minutes
- Minimum message age: 20 minutes
- Maximum cards per run: 20
- Maintainer detection: Slack users in the community workspace whose profile email ends in your maintainer domain (
$MAINTAINER_DOMAIN)
- Deduplication: skip any grouped item whose canonical source permalink already appears in recent destination-channel history
- Answered-card marking: add a ✅ reaction to prior destination cards once their source thread has a maintainer reply (24-hour lookback)
- Ownership source: your repository's
CODEOWNERS file (and any code-owner mapping you maintain)
- Answer preference source:
.agents/skills/triage-community-message/answer-preferences.md
Workflow
0. Choose an available Python 3
These scripts use only the Python standard library. Do not assume .venv/bin/python exists. From the repository root, choose an available Python 3 interpreter and reuse it for every command:
PYTHON="$(command -v python3 || command -v python)"
$PYTHON --version
If neither command exists, stop and report that Python 3 is unavailable.
1. Check token setup
Before running Slack API scripts, check whether COMMUNITY_SLACK_TOKEN and BUZZ_SLACK_TOKEN are present in the environment without printing their values. If either is missing, look for a local env setup file that can be sourced without printing secrets, such as .env, .env.local, .envrc, or a project-specific shell env file. Source it in the same shell invocation as the Slack command so tokens are available only to that process.
Do not echo, print, or write token values.
2. Collect recent source messages
Run from the repository root:
$PYTHON .agents/skills/triage-community-message/scripts/collect_messages.py --hours 1.5 --output /tmp/community_messages.json
This fetches top-level user messages from every source channel the community Slack bot belongs to, plus thread replies attached to those messages.
3. Filter unanswered messages
$PYTHON .agents/skills/triage-community-message/scripts/filter_unanswered.py /tmp/community_messages.json --output /tmp/community_unanswered.json
This removes messages posted by maintainers and messages whose threads already contain a maintainer reply.
4. Group bursts, apply grace period, and cap output
$PYTHON .agents/skills/triage-community-message/scripts/prepare_feed_items.py /tmp/community_unanswered.json --min-age-minutes 20 --limit 20 --output /tmp/community_feed_items.json
The script groups sequential top-level messages from the same author in the same channel when they are close enough to read as one thought. Each group has a canonical permalink: the first message's permalink, unless a thread makes another message the canonical reply location.
5. Deduplicate against #community-mentions
Use COMMUNITY_MENTIONS_CHANNEL_ID from the environment, then run:
$PYTHON .agents/skills/triage-community-message/scripts/filter_posted.py /tmp/community_feed_items.json --channel "$COMMUNITY_MENTIONS_CHANNEL_ID" --output /tmp/community_new_items.json
The script reads recent destination-channel history using BUZZ_SLACK_TOKEN and skips any item whose canonical source permalink is already present in a previous card.
6. Draft and post cards
For each item in /tmp/community_new_items.json:
- Use a simple relevance gate. Post only messages that likely need a maintainer reply, such as product questions, bug reports, feature requests, confusion, complaints, or substantive discussion. Skip pure acknowledgments, social chatter, and items already resolved by context.
- Identify likely topic.
- Identify the best person to answer for internal routing. Use the local
your-org/your-repo-ownership checkout as the primary source of truth for code ownership, using the STAKEHOLDERS files only as fallbacks when the local ownership checkout has no useful match. When a matched ownership area lists multiple owners, choose exactly one owner instead of listing every owner. Prefer a stable-random choice keyed by the item's canonical_permalink plus the matched ownership area name so repeated runs route the same community message to the same owner while distributing different messages across the owner set. Then check .agents/skills/triage-community-message/answer-preferences.md for additional preferences on interested parties; only include preference contacts when the preference explicitly applies to the item, and do not let preferences turn every multi-owner match into a broad owner list.
- Search GitHub for relevant open issues and collect links to likely matches. Always store
github_issues as a JSON array of link strings; use an empty array [] when there are no matches. Do not put the literal string none found in this field — the card script renders none found for an empty array.
- Use
draft-brand-reply for all voice, judgment, and topic-specific guidance when drafting the suggested reply. Do not add separate voice or tone rules here.
- Write the enriched cards to
/tmp/community_cards.json with a top-level cards array. Each card should include the original item fields plus author_display_name, suggested_reply, github_issues (a JSON array, empty when none), and display_name for the selected best person to answer. Preserve canonical_permalink exactly as provided so deduplication works on later runs.
- Post one top-level Slack card to
#community-mentions using post_cards.py: $PYTHON .agents/skills/triage-community-message/scripts/post_cards.py /tmp/community_cards.json --channel "$COMMUNITY_MENTIONS_CHANNEL_ID".
Card structure:
- Header:
*#<source-channel>* · *<author display name>* · <source permalink|time →>
- Body: blockquoted excerpt of the grouped message content
- Suggested reply:
*Suggested reply* ✅ plus the draft in a copyable preformatted block
- GitHub issues: relevant open issue links, or
none found
- Best person to answer:
*Best people to answer* plus the selected owner's best available @-mention on Slack
- To explicitly @-mention a person, call Slack
users.lookupByEmail with BUZZ_SLACK_TOKEN and the person's work email (in your maintainer domain), then render the mention as <@SLACK_USER_ID>.
- If lookup succeeds, set
display_name to <@SLACK_USER_ID>.
- If lookup fails or the email is missing, do not fabricate a mention. Fall back to the owner's display name, GitHub handle as inline code, or
unknown.
- Context:
Source: <source permalink>
Post with username Community mention • Buzz, unfurl_links: false, and unfurl_media: false.
7. Overflow note
If /tmp/community_new_items.json contains an overflow_count greater than zero after deduplication, post one compact note to #community-mentions after the cards:
+N more unanswered community messages were eligible this run but were capped. They may appear in a later run if still unanswered and inside the lookback window.
8. Mark answered cards
Maintainers often reply to the source message but forget to add a ✅ to the forwarded card. Every run, after posting new cards:
$PYTHON .agents/skills/triage-community-message/scripts/mark_answered.py --channel "$COMMUNITY_MENTIONS_CHANNEL_ID"
The script scans recent destination-channel cards, checks each card's source thread for a maintainer reply, and adds a white_check_mark reaction to answered cards. Cards that already have a ✅ are skipped. Run this step even when there are no new cards to post.
Constraints
- Do not post replies into community channels.
- Do not post skip/no-op items to
#community-mentions.
- Do not fabricate product behavior, policies, timelines, fixes, account decisions, issue links, codeowners, or best people to answer.
- Do not include a "why reply" field.
- Do not print, log, or save Slack token values.
- Keep each card suitable for a human maintainer to copy, edit, and send.