Files
claudetools/.claude/skills/ask-forum/SKILL.md
Howard Enos 0a438929d0 ask-forum: harden per high-effort review + document capability limits
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>
2026-07-07 20:37:46 -07:00

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-dmmike), 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 after cursor each cycle, so a reply is found even behind many bot/overflow messages (no false timeout).
  • Rate-limit aware: a 429 is honored (sleeps retry_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).
  • --wait baseline 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, --wait on 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-counting LC_ALL=C could 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 a thread_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 — @everyone View 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.yaml field credentials.bot_token), falling back to projects/discord-bot/.env DISCORD_TOKEN — works from any machine.
  • Engine: .claude/scripts/ask-forum.sh. Command entry point: /ask-forum. Related: discord-dm (person-targeted DMs / channel posts).