ワンクリックで
sweep-channels
Scan all bot-accessible Slack channels for untracked backlog items, cross-post approved candidates to
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Scan all bot-accessible Slack channels for untracked backlog items, cross-post approved candidates to
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Refresh priority signals, manage active epics, and refresh Near Term pool
Multi-session deliverable play for projects spanning 3+ sessions with concrete outputs (proposals, strategies, wireframes). Provides project-level structure, evidence provenance, and cross-session handoff.
Investigation-driven card grooming — investigate across data sources, synthesize findings into card content, present for approval
Use when acting as a reviewer in an AgenTerminal review conversation. Handles both code reviews (REVIEW_APPROVED) and plan reviews (PLAN_APPROVED).
Match Slack
Weekly release impact review — pull PostHog data for Released cards and tracked PRs, classify, draft observations, post to Slack
| name | sweep-channels |
| description | Scan all bot-accessible Slack channels for untracked backlog items, cross-post approved candidates to |
| disable-model-invocation | true |
Scan all Slack channels the FeedForward bot has access to for messages that could be backlog items but aren't yet tracked. Review findings with the user, then cross-post approved items to #ideas and add external links to existing cards where Slack threads add evidence.
This is the upstream feeder for /sync-ideas. Sync-ideas processes #ideas only; this play finds signal buried in other channels and surfaces it to #ideas.
Before starting, read these references:
reference/tooling-logistics.md: tested Slack and Shortcut API recipes.
Don't re-derive pagination patterns, reaction endpoints, or external_links
payload shapes from scratch.box/shortcut-ops.md: Shortcut constants, search operators, and update
recipes — needed for dedup (Step 5) and external link updates (Step 8).$ARGUMENTS is optional:
--days N: Override the watermark-based scan window. Applies to all
channels including new ones.--days 90 for new
channels.agenterminal.execute_approved.Retry-After.reference/tooling-logistics.md
for tested recipes. Don't re-derive payload shapes or endpoint paths.shortcut-mutate.py for all Shortcut
mutations (add-link, set-field, move, etc.) instead of raw API calls.
external_links and other array fields use replace-all semantics on PUT —
raw calls silently delete existing entries. The wrapper scripts handle
read-merge-write with built-in verification. Proved 2026-05-01.| Constant | Value |
|---|---|
| #ideas channel ID | C0ADJ4ATJE4 |
| Watermark key | slack-backlog-sweep |
| Channel | ID | Signal expectation |
|---|---|---|
| #product-feedback | C04DURMU9B2 | High — direct user feedback |
| #cs-squad-questions | CK4TLM9TR | High — customer-facing issues |
| #cs-squad | CDC3U0SUW | Medium — CS team ops, weekend Intercom summaries |
| #engineering | CJ4S4GE76 | Medium — tech debt, bugs |
| #feature-launch | C0E586F33 | Low — mostly ship announcements |
| #post-release-measurement | C0AGD4ZEC6M | Medium — measurement gaps |
| #proj-duck-keywords | C08CGT97BEZ | Medium — project-specific |
| #proj-personalized-onboarding | C0AJV066CSX | Medium — project-specific |
| #proj-smartpin | C072YAKP4RX | Medium — project-specific |
| #proj-tailwind-turbo | C09762G179D | Medium — project-specific |
| #bug-reporting | C04CYGR7NKY | High — bugs with jam.dev recordings |
| #daily-digest | C0ASG05F1SB | Derivative — see note below |
| #general | C026G3XB4 | Low — high noise |
| #share-the-love | C035A1VQY | Low — celebrations |
| #ideas | C0ADJ4ATJE4 | Special — scan for untracked items already here |
Update this table when new channels are discovered in Step 1.
#daily-digest is derivative, not primary. This channel contains FeedForward Bot's automated Intercom daily summaries. Every signal it surfaces (bug reports, feature requests, pain points) originates from Intercom conversations that are tracked through the Intercom sync pipeline and surface independently in CS channels (#cs-squad, #cs-squad-questions). Classify all #daily-digest messages as not_actionable. Do not cross-post from this channel — it would create duplicates of items that arrive through primary channels. Scan it only to confirm no human-posted messages are being missed.
Get the full list of channels the bot is a member of. Always paginate —
conversations.list paginates even with limit=999.
# Paginate all pages of conversations.list
# See reference/tooling-logistics.md for token loading pattern
import json, urllib.request
token = ... # grep from .env
members = []
cursor = ''
while True:
url = f'https://slack.com/api/conversations.list?types=public_channel&limit=999&exclude_archived=true'
if cursor:
url += f'&cursor={cursor}'
req = urllib.request.Request(url, headers={'Authorization': f'Bearer {token}'})
d = json.loads(urllib.request.urlopen(req).read())
for c in d.get('channels', []):
if c.get('is_member'):
members.append((c['id'], c['name']))
cursor = d.get('response_metadata', {}).get('next_cursor', '')
if not cursor:
break
Compare against the Known channels table above. New channels (not in the table) have no watermark — they get a full-history scan.
First run (no prior state): If no watermark exists and the Known channels
table has never been updated, treat all discovered channels as new — apply
--days 90 to all.
Report the channel list before proceeding: how many channels, any new ones discovered, any previously-known channels where the bot is no longer a member.
python3 box/watermark.py get slack-backlog-sweep --days
If it prints a number, use that as --days for channels in the Known channels
table. If none, fall back to --days 90. New channels (not in the Known
channels table) always get --days 90 regardless of watermark.
If $ARGUMENTS includes --days N, use that for all channels.
Delegate to subagents via agenterminal.delegate in batches of 3 channels.
Don't set timeout_ms — the default is the maximum (30 min).
Each subagent runs two commands per channel:
# 1. Save human-readable summary to raw files (audit trail)
python3 box/slack-scanner.py --channel {ID} --days N --untracked-only --summary \
> box/research/slack-backlog-sweep-raw/{channel-name}.txt 2>&1
# 2. Fetch full JSON for classification (includes thread context)
python3 box/slack-scanner.py --channel {ID} --days N --untracked-only
Each subagent:
box/research/slack-backlog-sweep-raw/{channel-name}.txtSubagent instructions: Save raw files first, then classify from the full JSON. This decouples API work from classification — if the subagent times out during classification, the raw files survive for a re-dispatch that reads from disk instead of calling the API again.
Use output_instructions to constrain return shape:
Return a JSON object:
{
"channels": [
{
"name": "#channel-name",
"total_untracked": number,
"candidates": [
{
"permalink": "full slack permalink URL",
"author": "display name",
"date": "YYYY-MM-DD",
"summary": "one sentence",
"backlog_signal": "feature_idea | bug_report | pain_point |
workflow_friction | measurement_gap | not_actionable",
"reasoning": "why this classification"
}
]
}
]
}
Include ALL untracked messages. Classify each one — don't filter.
Timeout escalation: If a batch times out at 10 minutes, check whether raw files were saved. If yes, re-dispatch pointing at the saved files (no API calls, classification only). If no raw files, split the batch into individual channels and re-dispatch each separately. Do not absorb the work into primary context.
Dispatch and collection: Dispatch all batches in parallel. Collect results
as they complete (parallel agenterminal.collect calls). After collecting each
batch, report to the user: which channels finished, how many untracked messages
found, whether any channel returned suspiciously high or zero volume.
For messages classified as potential backlog items, read the full thread context
in main context using the scanner's --threads mode:
python3 box/slack-scanner.py --channel {ID} --threads permalink1,permalink2
Don't present subagent summaries as findings — verify the classification against the actual thread content.
When a thread includes customer identifiers or references an Intercom conversation, follow them. CS threads often contain Jarvis links, org IDs, emails, or phrases like "I have someone writing in." These point to the actual Intercom conversation — the Slack thread is the relay, not the source. Search the Intercom index by those identifiers and read the conversation as part of this step. The primary source may contain discriminating details (slot transfers, account-specific failures, prior history) that the relay omits or de-emphasizes. Proved 2026-04-27: relay said "can't add pins to Turbo"; Intercom conversation revealed slot-transfer trigger that changed classification from SC-1541 external link to new cross-post.
After reading all threads, produce a reclassification table before proceeding to Step 5. For each channel where the subagent reported >0 signal items, show:
| Channel | Subagent classification | Your classification | Changed? | Rationale |
Count matching (raw file message counts match subagent totals) is NOT classification matching — they are separate verification claims. When presenting spot-check results, state which claim the check verified. Proved twice (2026-04-20, 2026-04-21): ✅ on count match presented as full confirmation, masking silent reclassification of 5 signal items to 0.
This step runs in main context, not delegated. The dedup involves judgment calls (is this card the same scope?) that require reading actual cards. The first run had a proxy trust failure when dedup was delegated and the results were presented without verification.
Search existing Shortcut cards across ALL states including Released (items within the scan window could have been carded, fixed, and shipped).
Search order:
python3 box/shortcut-cards.py --active --summarypython3 box/shortcut-cards.py --state "STATE" --summary
python3 box/shortcut-cards.py --product-area AREA --active --summary
# Keyword search via Shortcut API for specific terms
Prior sweep runs add :shortcut: reactions to external-linked threads (Step 8),
so --untracked-only filters them out before they reach this step. For
cross-posted items, the card /sync-ideas creates from the #ideas message is
the dedup target — search by content similarity, not by Slack permalink.
Verification gate: Before presenting dedup results, directly read (via
shortcut-cards.py --id) every card cited as a YES match and verify every NO
match with the additional searches above. If all searches return no match,
classify as cross-post.
Scope verification for proposed matches: When proposing a card as a dedup
match, state the candidate's product area and the proposed card's product area.
If they don't align, it's not a dedup — it's a different feature that happens
to share a keyword. Proved 2026-04-23: linked a Turbo extension language filter
request to SC-245 (Keywords: Enable language filters in Keyword Search) based
on 'language filter' keyword match. User caught the scope mismatch.
Consult .claude/rules/product-knowledge.md for known product area boundaries
and entity scope distinctions.
Codebase verification for feature requests: For candidates classified as feature_idea, pain_point, or workflow_friction (not bugs), verify whether the requested capability already exists in the codebase before classifying as cross-post. Run in main context (same rationale as dedup — it gates a mutation). The check is targeted: grep the relevant feature area for UI components, API endpoints, or feature flags matching the request. Shortcut tracks planned and in-progress work — shipped features that predate the card system, or whose card titles don't match user language, are invisible to card search. Proved 2026-04-15: Turbo report/block/hide fully shipped with 7 report reasons including "Inauthentic/misleading AI content," but no card title matched "AI image report."
Classify each candidate:
Check for PRs before cross-posting bug reports. When a thread has
someone saying "I'll fix it" or "I'll get a fix up," search GitHub PRs
(gh pr list --repo tailwind/aero --search) before classifying as cross-post. The fix may
already be merged, making a card redundant. Proved 2026-05-01: Bill's
PR #3542 (filter malformed pin images) was merged same day as the report,
caught by PR search before cross-posting.
External link verification gate: For every candidate classified as
external link, read the target card (shortcut-cards.py --id) and verify:
Proved 2026-04-29: assumed a SmartPin scrape failure matched SC-1737 (Lambda timeout) based on "scrape failure + intermittent" without verifying the failure mechanism. User caught the ungrounded assumption. Separately, SC-884 was grouped with SC-882 in presentation, approved for SC-882 only, and SC-884 was silently dropped from scope.
Each external link is a separate approval item. When a thread maps to multiple cards (e.g. SC-882 and SC-884), present each card link separately. Grouping cards in presentation creates silent drops when only one is approved.
#ideas items: Items found in #ideas that are untracked should NOT be classified as cross-post candidates (they're already in #ideas). Flag them as "needs /sync-ideas tracking" in the review surface. /sync-ideas handles Shortcut card creation for #ideas messages.
Build box/research/slack-backlog-sweep-candidates.md with:
Present via approve_content with content_type: "sweep-candidates",
filename: "sweep-YYYYMMDD". The saved file is compaction insurance — if
context compacts before Step 7, read the saved file to recover the approved
list.
Present items one at a time (not as a batch). This gives the user room to explain reasoning per item and steer decomposition decisions.
The user decides:
Execute only the items the user confirmed in Step 6. Step 6 is the per-item approval; this step executes the approved subset.
Format: <permalink|Distilled idea title>
Slack auto-unfurls the linked message. /sync-ideas picks these up on future passes and creates cards + adds tracking markers on the #ideas message.
Do NOT add :shortcut: reactions or tracking replies to source threads in the originating channels. No card exists yet for these items. /sync-ideas handles tracking when it processes the #ideas cross-post and creates a card.
Use slack-mutate.py post for each cross-post via execute_approved:
python3 box/slack-mutate.py post C0ADJ4ATJE4 '<permalink|title>'
The script posts and verifies the message is present in the channel.
Check the script's verification output (exit code 0 = verified) before
proceeding to the next post. One execute_approved call per post.
For each approved external link addition, use the safe wrapper:
python3 box/shortcut-mutate.py add-link STORY_ID PERMALINK
This handles read-merge-write (reads existing links, appends, PUTs full
array) and verifies the link is present afterward. Check the script's
verification output (exit code 0 = verified, 1 = verification failed)
before proceeding. Do NOT use raw GET/PUT — external_links is a
replace-all array field.
After updating the card, add tracking markers to the source thread:
python3 box/slack-mutate.py react CHANNEL_ID THREAD_TS shortcut
python3 box/slack-mutate.py reply CHANNEL_ID THREAD_TS 'Linked to <shortcut_url|SC-NNN: Story title>'
Check each script's verification output (exit code 0 = verified) before
proceeding to the next mutation. One execute_approved call per mutation.
The reply text says "Linked to" (not "Tracked") to distinguish from
/sync-ideas card creation.
box/research/slack-backlog-sweep-brief.md:
agenterminal.execute_approved (the production mutation
gate blocks this command): python3 box/watermark.py set slack-backlog-sweepDo not delete or overwrite box/research/slack-backlog-sweep-brief.md —
append new completion records. The file contains the channel list audit trail
needed for new-channel detection on future runs.
Skill file updates: The Write tool is blocked on .claude/skills/. Use
the Bash heredoc workaround to update the Known channels table in this file.
See feedback_write_tool_skills_dir.md.
Before each mutation:
external_links array
for the permalink. If present, skip.already_reacted from Slack = success, not error.If the watermark doesn't advance (e.g. Step 9 is interrupted), the next run rescans the same window. The dedup step (Step 5) and these idempotency checks prevent duplicate cross-posts and links.
After a complete run:
box/research/slack-backlog-sweep-brief.md has a new completion recordbox/research/slack-backlog-sweep-raw/ has per-channel scan filesbox/research/slack-backlog-sweep-candidates.md overwritten with this run's
reviewed surface (one file per run, not appended)external_links arraysconversations.list paginates even with limit=999. Always follow
next_cursor.--untracked-only filters on :shortcut: reaction, bot reply, and human
Shortcut link. Cards may exist without the Slack thread being linked.
Shortcut dedup (Step 5) catches these.> in link text breaks Slack mrkdwn. Check cross-post titles before posting.json.dumps() for all Slack API calls.