Fixes from the code-review workflow (7 findings): page the poll cursor past the 100-msg window (no false timeout behind bot/overflow messages); honor 429 retry_after and bail fast on permanent Discord errors (10003/50001/...) instead of burning the timeout; status-check overflow follow-up chunks (warn, don't silently truncate); clamp --read limit safely (guard >64-bit overflow); add --wait --after for precise resumption; DRY the newline-trim; set LC_ALL=C.UTF-8 for codepoint-safe slicing. SKILL.md gains the tested bot capability/limit map (can: post/read/edit-own/react; cannot without a grant: delete/archive/unlock/pin) and a robustness section. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.9 KiB
name, description
| name | description |
|---|---|
| ask-forum | Ask a teammate (Mike/Winter/Howard/anyone with forum access) a question in the private #ct-forum Discord forum and get their human answer back IN THIS SAME SESSION, then act on it - a live three-way between the user, this Claude session, and the teammate. No second bot, no DMs, one tool call (the wait runs server-side in bash, zero tokens while waiting). Triggers: ask Mike/Winter in the forum, ask the team, post a question to ct-forum, get a human answer/decision/sign-off, human-in-the-loop, run something by <person>, wait for their reply. |
ask-forum — human-in-the-loop questions via #ct-forum
Thin wrapper over .claude/scripts/ask-forum.sh. Lets THIS session put a question to a
human in the private #ct-forum Discord forum and receive their answer back in-session,
so it can keep going. Use it whenever you need a person's answer, decision, or sign-off
mid-task and want it to flow back to the running session — instead of stopping and asking
the user to relay it.
USE THIS SKILL — do not hand-roll curl against the Discord API or the bot token.
Free-handing the raw call is exactly what produced the &-on-background bug (a wait that
exited instantly and never delivered the answer). The script + this contract encode the
correct thread-create, the non-bot reply filter, and the background-wait discipline. Reach
for raw API only if this skill genuinely can't do what's needed — and say so.
When to use
- You need a teammate's answer / decision / approval and want it back in this session (e.g. "ask Mike whether to deploy", "run this naming by Winter", "get a go/no-go").
- A back-and-forth where the human's reply should drive the session's next action.
Not for: one-way copy-paste delivery of a link/command (that's discord-dm → mike), or
routine [SYNCRO]/[RMM] status alerts (post-bot-alert.sh).
Usage
# Ask + BLOCK until a human replies, then act on the answer:
bash .claude/scripts/ask-forum.sh "your question" --tag @264814939619721216
# Re-read a thread you already asked in (one-shot, no waiting):
bash .claude/scripts/ask-forum.sh --read <thread_id>
# Resume blocking on an already-posted thread (e.g. after a timeout):
bash .claude/scripts/ask-forum.sh --wait <thread_id> --timeout 1800 --poll 10
# add --after <msg_id> to wait for replies STRICTLY AFTER a message you already
# consumed (default baseline catches any reply since the thread's starter).
Robustness (hardened after the 2026-07-08 review)
- Pages past the 100-message window: the poll advances its
aftercursor each cycle, so a reply is found even behind many bot/overflow messages (no false timeout). - Rate-limit aware: a
429is honored (sleepsretry_after); a permanent Discord error (Unknown Channel 10003, Missing Access 50001, etc.) bails immediately instead of burning the full timeout; only genuine transients keep polling. - Long questions (>1900 chars) chunk into the starter + follow-up messages, and each overflow POST is status-checked (warns if one drops, so the question isn't silently truncated).
--waitbaseline is the thread's starter id (starter id == thread id for a forum post), so a reply that arrived before the wait started is caught, not missed. Because of this,--waiton a thread that already holds a consumed answer re-returns it — pass--after <msg_id>when you only want the NEXT reply.- Residual caveat: content slicing assumes a UTF-8 locale (the script sets
LC_ALL=C.UTF-8); a forced byte-countingLC_ALL=Ccould split a multibyte char at the 1900-char cut. Not an issue under the fleet default locale.
Flags: --title "short title" (forum post name; defaults to first 60 chars of the
question) · --timeout SEC (default 600) · --poll SEC (default 8) · --tag @<id>
(repeatable — pings that person). Discord IDs: mike 264814939619721216,
howard 624667664501178379, winter 624666486362996755, rob 261978810713505792
(also in .claude/users.json).
HARD RULE — long waits run as a proper background task (no shell &)
A blocking --wait/ask can sit for many minutes. Run it with the tool's
run_in_background: true and nothing else — do NOT append a shell &, disown,
or nohup. Adding & forks the wait off and the shell exits 0 immediately, orphaning the
poll loop so the answer is never delivered (logged friction, twice). Done right, the wait
costs zero tokens while pending and the harness notifies you the instant a human
replies — then you surface the answer to the user unprompted. See
feedback_background_task_no_ampersand.
How it works / correlation
- Posts a forum post via
POST /channels/<forum>/threads, returns athread_id. - The session reads only its own
thread_id, so an answer can never be confused with another question's. Replies route to whoever asked — Mike's thread → Mike's session, yours → yours, no crossover. - Any non-bot reply counts as the answer (first human wins). Anyone with #ct-forum access can answer; the script never filters by person. Bot chatter is filtered out.
Model & scope (decided with Mike, 2026-07-08)
- Forum-only. No DM replies — answers stay team-visible.
- Good-enough durability: answers are captured while the session is open; Discord keeps
the history, so any thread is cheap to re-read (
--read) later in the same session. It is NOT restart-safe (close the session → nothing is watching); that was an accepted tradeoff for the cheapest option. Full restart/close-safe capture would be the coord-backed registry or the event-driven bot-router — build only if asked. - The BEAST bot ignores #ct-forum (patch in
projects/discord-bot/bot/main.py) so it doesn't auto-answer and collide with the asking session.
Access & permissions
- Channel: #ct-forum (
1522960388432465950), private —@everyoneView DENY; Howard + the bot explicitly allowed; Mike sees it as owner. To let someone else answer, they need View Channel on #ct-forum (grant a role like Techs, or the person). Winter/Rob are not in by default.
Bot capability map — the limit of control (tested 2026-07-08)
What the bot can do in #ct-forum with its current permissions, and where the wall is:
| Bot CAN (no grant needed) | Bot CANNOT (needs a permission grant) |
|---|---|
| Read messages / thread / members / guild channels | Pin a message → 403 (needs Manage Messages) |
| Create a forum post (thread), post follow-up messages | Delete a thread → 403 (needs Manage Threads) |
| Edit its OWN messages; add/remove reactions; typing | Archive a thread → 403 (needs Manage Threads) |
| Rename / lock a thread it owns (see trap below) | Unlock a thread once locked → 403 (needs Manage Threads) |
- One-way trap: as thread owner the bot can lock a thread (200) but then cannot unlock or rename it (403) — locking without Manage Threads is irreversible. So the skill does NOT lock/rename threads; treat thread lifecycle as off-limits until granted.
- To enable cleanup: owner grants the ClaudeTools bot Manage Threads on #ct-forum (Edit Channel → Permissions → bot → Manage Threads → Allow). Then delete/archive/unlock work. Manage Messages additionally enables pin.
- Reactions + own-message edits DO work with no grant — usable for lightweight acknowledgement (react to a reply, or edit the question to "[answered]") if ever wanted.
Exit codes
0 answered · 1 usage · 2 no token · 3 Discord API error · 4 timeout (thread stays
open — resume with --wait <thread_id>).
Implementation notes
- Bot token resolves from the SOPS vault
(
projects/discord-bot/bot-token.sops.yamlfieldcredentials.bot_token), falling back toprojects/discord-bot/.envDISCORD_TOKEN— works from any machine. - Engine:
.claude/scripts/ask-forum.sh. Command entry point:/ask-forum. Related: discord-dm (person-targeted DMs / channel posts).