ask-forum: promote to a full skill + wire into skill-first routing
Add .claude/skills/ask-forum/SKILL.md (usage contract, correlation model, forum-only scope, access/permissions) and route to it from CLAUDE.md (skill-first covered domains) + SKILL_ROUTING.md, so sessions invoke the skill instead of hand-rolling the Discord API — the footgun that produced a broken background wait. Capture that footgun as memory feedback_background_task_no_ampersand (run_in_background, never a shell &). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -44,7 +44,9 @@ production, data-loss. Detail: EXTENDED + `.claude/OLLAMA.md`.
|
||||
records (e.g. Syncro tickets/invoices) reach a human for cleanup. **Syncro billing/invoicing
|
||||
ALWAYS runs through `/syncro` (or `/syncro-emergency-billing`) — no exceptions.** Same for
|
||||
other covered domains: credentials → `vault`, RMM actions → `/rmm` (+ `rmm-search` to find a
|
||||
host), M365 → `remediation-tool`, etc. Knowing the API is NOT a reason to bypass the skill —
|
||||
host), M365 → `remediation-tool`, asking a human a question/decision in Discord (get their
|
||||
reply back in-session) → `ask-forum` (never hand-roll the Discord API — that footgun cost us
|
||||
a broken background wait), etc. Knowing the API is NOT a reason to bypass the skill —
|
||||
the memory rules (e.g. [[feedback_syncro_billing]]) describe what the SKILL does, not a license
|
||||
to free-hand it. Reach for raw API ONLY when no skill fits or the skill genuinely cannot do it
|
||||
— and say so explicitly when you do. Mistakes here go to `errorlog.md` (`--correction`).
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
| UniFi WiFi tuning / RF / channel analysis | `unifi-wifi` |
|
||||
| Map/repoint a Windows network drive on a remote endpoint | `drive-map` |
|
||||
| Send someone a Discord DM / copy-paste-friendly link or command | `discord-dm` |
|
||||
| Ask a teammate a question/decision/sign-off and get their human answer back in-session | `ask-forum` (run the `--wait` as a proper background task — NO shell `&`) |
|
||||
| Inter-session messaging, fleet todos, resource locks, coord status | `coord` |
|
||||
| Git / Gitea / submodules / sync health | `gitea` (or `/sync`, `/scc`, `/save` for session ops) |
|
||||
| Build/verify GuruRMM agent/server/dashboard, pre-merge check | `gururmm-build` |
|
||||
|
||||
@@ -5,7 +5,8 @@ description: Ask a teammate (e.g. Mike) a question in the private #ct-forum Disc
|
||||
|
||||
# /ask-forum — human-in-the-loop question via #ct-forum
|
||||
|
||||
Thin entry point to `.claude/scripts/ask-forum.sh`. Lets THIS session put a question
|
||||
Entry point to the `ask-forum` skill — full contract in `.claude/skills/ask-forum/SKILL.md`.
|
||||
Engine: `.claude/scripts/ask-forum.sh`. Lets THIS session put a question
|
||||
to a human in the private #ct-forum forum and get their answer back in-session, so it
|
||||
can keep going. The forum post's thread id is the correlation key — the session reads
|
||||
only the thread it created, so an answer is never confused with another question's.
|
||||
|
||||
@@ -225,3 +225,5 @@
|
||||
- [Fire #dev-alerts for client/RMM work](feedback_fire_dev_alerts_for_client_work.md) — Howard+Mike watch #dev-alerts Discord for live visibility into what techs/sessions touch. Fire post-bot-alert.sh ([DEV]/[RMM]/[DEPLOY]/[GURURMM] prefix -> #dev-alerts) at the START of any RMM/Dev or active-client-machine action. No CLAUDE.md rule yet — this memory is the rule.
|
||||
- [M365 app: SharePoint needs CERT](reference_m365_app_sharepoint_rest_vs_graph.md) — SP app-only works via the CERT (get-token.sh <tenant> sharepoint); the secret gives "Unsupported app only token". Graph app-only uses the secret.
|
||||
- [EDR auto-isolates GuruRMM PowerShell](project_edr_rmm_autoisolation_fp.md) — Datto "Exfiltration Over HTTP" rule auto-isolates benign GuruRMM scripts fleet-wide (vwp-qbs 4h outage); watcher + narrow suppression deployed, policy fix pending Mike
|
||||
- [Background tasks — no shell `&`](feedback_background_task_no_ampersand.md) — with run_in_background:true, run the command in the foreground of the shell; adding `&`/`disown` forks + exits 0 instantly, orphaning a blocking wait so it never delivers (hit twice building ask-forum)
|
||||
- [ask-forum — human-in-the-loop via #ct-forum](../skills/ask-forum/SKILL.md) — ask a teammate a question in the private ct-forum Discord forum, get their human answer back in-session; forum-only, any human can answer, run the --wait as a proper background task
|
||||
|
||||
26
.claude/memory/feedback_background_task_no_ampersand.md
Normal file
26
.claude/memory/feedback_background_task_no_ampersand.md
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
name: Background tasks — run_in_background only, never add a shell `&`
|
||||
description: When launching a long-running command as a background task (run_in_background:true), do NOT also append a shell `&`/`disown`/`nohup`. The shell forks the command and exits 0 immediately, orphaning it — a blocking wait then never delivers its result. Run it in the FOREGROUND of that shell; the harness does the backgrounding.
|
||||
type: feedback
|
||||
---
|
||||
|
||||
When you want a long-running command (e.g. `ask-forum.sh --wait`, a poll loop, a blocking
|
||||
watcher) to run in the background, use the Bash tool's **`run_in_background: true`** and run
|
||||
the command in the **foreground of that shell** — a bare `bash script.sh ...` with **no
|
||||
trailing `&`, no `disown`, no `nohup`**.
|
||||
|
||||
**Why:** appending `&` forks the command off inside the shell; the shell then reaches the
|
||||
end and exits **0 immediately**. The harness sees exit 0 and reports the task "completed"
|
||||
right away, while the forked process is orphaned (and often killed). A blocking `--wait`
|
||||
that was supposed to sit for minutes and deliver an answer instead returns nothing — its
|
||||
result is never captured or notified.
|
||||
|
||||
**How to apply:**
|
||||
- Background wait, CORRECT: `Bash(command="bash .claude/scripts/ask-forum.sh --wait <id> --timeout 1800", run_in_background=true)` — the wait truly blocks server-side, costs zero tokens while pending, and the harness pings you the instant it completes.
|
||||
- WRONG: `Bash(command="bash ... --wait ... &", run_in_background=true)` and `... & disown` — orphans the wait, "completes" instantly, no answer.
|
||||
- The two mechanisms are redundant: `run_in_background` IS the backgrounding. Never stack a shell `&` on top of it.
|
||||
|
||||
**Incident:** hit TWICE in one session (2026-07-08) building the [[ask-forum]] flow — each
|
||||
time the "background wait" for a Discord reply exited 0 instantly and never delivered the
|
||||
answer, which looked like the reply was lost and forced the user to manually prompt "did
|
||||
they answer?". Logged as `--friction` in `errorlog.md`. Related: [[ask-forum]].
|
||||
92
.claude/skills/ask-forum/SKILL.md
Normal file
92
.claude/skills/ask-forum/SKILL.md
Normal file
@@ -0,0 +1,92 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
|
||||
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.
|
||||
- **Deleting threads** needs the bot to have **Manage Threads** on #ct-forum (owner grants
|
||||
it). Without it, `DELETE /channels/<thread>` returns 403.
|
||||
|
||||
## 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).
|
||||
Reference in New Issue
Block a user