---
name: ask-forum
description: "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

```bash
# 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).
