| name | flow-slack |
| description | Delegate flow tasks to teammates via Slack threads — proactively.
CLI binary `flow-slack` on $PATH; SQLite-backed state at
~/.config/flow-slack/flow-slack.db; auto-surfaces both pending
kickoff prompts and new replies via the UserPromptSubmit hook.
USE THIS SKILL whenever any of these happen:
• The user uses any "assign" / "delegate" / "hand off" / "ping" /
"give to" verb about a flow task — "assign this to X", "assign
this task to Y", "delegate this", "hand off to Z", "ping
@someone". THIS SKILL OWNS THE "ASSIGN" VERB FOR FLOW TASKS;
the `flow` skill's `--assignee` flag is a sub-step this skill
drives, NOT a replacement. Even on bare "assign this task to
<name>", invoke flow-slack — it will handle both the flow-side
assignee update AND the Slack delegation post in the right
order. Do not let the `flow` skill swallow these phrases.
• The hook injects a "Pending kickoff posts" or "New replies in
delegated threads" system reminder. ACT on it immediately —
these are explicit asks from the user's task state.
• The user wants reassignment — "reassign to Y", "actually give
this to Z", "switch the owner".
• The user wants status — "any update from X", "what is X
saying", "have they replied".
• The user wants to post or nudge — "tell X that …", "reply with
…", "post in the thread …", "ping the assignee".
• The user wants to share a self-broadcast update — "log that I
finished X" / "note that I'm starting Y" / "I'm picking up Z".
In self mode, ALWAYS ask before posting to channel.
• Bare CLI mentions — anything starting with `flow-slack ...`.
• The user asks about new tasks to pick up — "any new tasks",
"anything in my inbox", "what got dropped on me", "what should
I look at next from Slack". ALWAYS check the inbox: render
pending items from the hook OR run `flow-slack inbox list`,
then walk the user through triaging each one.
• The user has updated a flow task that has a linked Slack
thread, and the update would be meaningful to the people in
that thread. Run `flow-slack status <slug>` to check whether a
thread exists, and if so AskUserQuestion before mirroring the
update via `flow-slack reply <slug>`. Defaults by mode:
self → yes (it's your log), origin → ask (responding to the
original asker is usually wanted), delegated → no (their thread).
• Before EVERY `flow-slack assign` or `flow-slack broadcast`
invocation, ASK THE USER which channel to post to. There is no
default channel — both verbs REQUIRE --channel <C> and will
error without it. Phrase the prompt with channel names the user
will recognize (e.g. "#flow-tasks", "#project-vault"); ask the
user to confirm or paste a channel ID if you don't already know
it. Never assume.
DO NOT use this skill for general Slack messaging unrelated to a
flow task, for questions about flow tasks that aren't about
delegation or sharing ("what's on my plate" → use the `flow`
skill), or for chatting with someone in a non-delegation channel.
|
§0. The channel rule (read first)
flow-slack v3 has no default channel. flow-slack assign and
flow-slack broadcast REQUIRE --channel <C> on every call and
will error without it. The user picks the channel per task — by
NAME, not opaque ID.
Before invoking either verb, ALWAYS:
- Run
flow-slack channels list to see channels the user has
previously interacted with. Output is TSV: id name last_used use_count,
most-recent-first.
- AskUserQuestion showing the top 3–5 known channels BY NAME as
options, plus "Other — paste channel ID and name". If
channels list
returns "(no known channels)", skip to step 4 and ask for both.
- If the user picks a known channel, you already have its ID. Map
the chosen name back to the row, extract its
channel_id, pass it.
- If the user picks "Other", AskUserQuestion for the channel ID
(e.g.
C0123456789) AND a friendly name (e.g. #project-vault).
Then either pass --channel-name "<name>" along with --channel
on assign/broadcast (preferred — atomic), OR run
flow-slack channels remember <id> <name> before the kickoff.
- Pass
--channel <C> --channel-name "<name>" on the resulting
flow-slack assign|broadcast call. The verb auto-records the
pair into known_channel for next time.
If flow-slack channels list errors (e.g. DB not yet created),
fall back to asking for the channel ID directly. Don't block.
This rule applies even when reassigning — channel choice is
intrinsic to the kickoff, not derivable from config.
§A. The full workflow (read this once, then use the per-step sections below)
flow-slack bridges flow tasks and Slack threads. The Slack thread is
the source of truth; each side's flow-slack.db is a local cache
of that thread plus a locally-chosen slug. Slugs can freely diverge —
only the thread can't.
Three scenarios, all use the same plumbing:
Scenario 1 — Delegated (you assign to a teammate)
-
You: create a flow task, complete the interview (brief.md
reaches ≥20 chars), pick a channel (§0 picker), draft a
one-liner. Run flow-slack assign <slug> <email> --summary "<one-liner>" --channel <C> --channel-name "<name>". Posts in
channel <C> a slugless header (:pushpin: priority • #tags • due <date>) + <@them> ping, then DMs the teammate the full
brief. Your DB: mode=delegated, channel=<C>, thread_ts=<T>.
-
Teammate (their Claude session): their inbox auto-detects
the <@them> kickoff (workspace-wide search.messages). They
ask "any new tasks?" → §2.3 surfaces it. Skill §1.3 detects the
flow-slack kickoff phrase ("I just DM'd you the full brief"),
asks them to pick a local slug (which may differ from yours)
and paste the brief from your DM. Their DB:
mode=origin, channel=<C>, thread_ts=<T> — SAME thread.
-
Teammate posts updates: flow-slack reply <their-slug> "...". Lands in channel <C>, thread <T> — the same thread
you see. Their @assignee token expands to YOU (the original
asker / active_user_id on their origin row).
-
You check status: the hook surfaces new replies on each
prompt; or on demand, "what's the status of <your-slug>?"
runs flow-slack new-replies <your-slug> (§2.2.b). Empty →
offer to nudge.
-
Teammate closes: flow-slack close <their-slug> --summary "<brief>". Posts :white_check_mark: Done. <@you> — closing the loop. _<brief>_ in the thread (no slug in the post).
-
You mirror: your hook surfaces the close reply. §2.2.a
detects the close pattern (thread-anchored) and prompts you
to flow mark done <your-slug> + flow-slack close <your-slug> --no-post (mirror; don't double-post the ✅).
Scenario 2 — Self (you create + work)
-
You: flow add task → interview completes. Hook surfaces
"Pending kickoff posts" §2.1. Pick a channel (§0 picker), draft
a one-liner. Run flow-slack broadcast <slug> --summary "<one-liner>" --channel <C> --channel-name "<name>". DB:
mode=self, channel=<C>, thread_ts=<T>. Channel post:
:memo: priority • #tags • due <date>\n\nAssign this task to me.\n\n_<summary>_ (slugless).
-
You post updates: flow-slack reply <slug> "<text>". Lands
in <C> / <T>. Anyone in the channel sees them.
-
You close: §7.1 draft + AskUserQuestion. Run flow-slack close <slug> --summary "<brief>". Posts :white_check_mark: I've completed this task. _<brief>_.
Scenario 3 — Their self (teammate's own task in a channel you're in)
You're a spectator. Their broadcast appears in the channel; you can
react as a human in Slack, but flow-slack doesn't touch this on
your side unless they <@>-ping you (self mode doesn't, by
design). Mostly nothing for Claude to do here.
Re-engagement
See §B. When the user mentions a slug that has an active thread, run
flow-slack show <slug> + flow-slack new-replies <slug> BEFORE
answering. Otherwise you risk stale context.
§B. Re-engagement: load the thread before responding
When the user references a flow task that has an active flow-slack
thread (any mode), ALWAYS load the thread context BEFORE answering.
Triggers include:
- The user mentions a slug by name ("how's
<slug> going?",
"let's pick up <slug>", "update <slug>").
- The user uses a
flow <verb> <slug> form that you're about to
proxy or react to.
- You're resuming a task after a context break (memory pulled in
from a previous session, or the user said "where were we on
<slug>?").
Two-call context load:
flow-slack show <slug> — JSON dump of the active thread
(mode, channel_id, thread_ts, permalink, active_user_id,
created_at). This is your map.
flow-slack new-replies <slug> — fetches and persists any
fresh replies; advances the watermark. Shows the curated reply
log for the deltas.
Frame your response around what you loaded. Example:
"Picking up oauth-budget (delegated to Alice, kickoff Tuesday
in #flow-tasks). Two new replies since I last checked: Alice
says she's blocked on the bucket-store choice; she's planning to
use Redis unless you object. What would you like to do?"
If show returns no active thread for the slug, just respond from
the flow task state — there's nothing to load. If new-replies
errors (e.g. transient Slack issue), surface a one-line warning and
proceed with the cached show data.
Don't reload unnecessarily. If you JUST ran show + new-replies
this turn (e.g. the hook surfaced fresh replies and you've already
acknowledged them), skip the reload. The rule fires on
re-engagement, not on every reference within a single turn.
flow-slack v3 — Slack-thread delegation + inbox skill
1. Three modes — internalize the asymmetry first
flow-slack v3 has three task modes. They differ in policy:
-
Delegated mode (task has an assignee that is someone else):
the channel thread is between the user and the assignee. Claude
does not volunteer posts. Only post into the thread when the
user explicitly asks ("tell Alice X", "reply with Y").
-
Self mode (no assignee — self-broadcast): the channel thread
is the user's public log. Claude proactively asks "post this
update to the channel?" on every flow update.
-
Origin mode (task was created from an inbound channel
@mention): the thread already exists in Slack; no kickoff post
is made. Updates flow back into the original mention's thread.
All modes except origin require Claude to draft + user-approve the
one-line task description before the kickoff post. The one-liner is
load-bearing — a downstream praxis bot scrapes channel posts and
creates GitHub issues from them.
1.1 "Assign to me" / "I'll take this" → broadcast, NOT delegated
When the user says any of:
- "assign this to me"
- "assign this task to me"
- "I'll take this on"
- "I'm picking this up"
- "give it to me"
Do not run flow update task --assignee <user> (that sets
flow's assignee column to the user, which routes the task into
delegated mode — and delegated mode would then try to DM the user
their own task, which is nonsense).
Do run flow-slack broadcast <slug> --summary "<draft>" after
the standard draft + user-approve dance from §2.1. The flow task's
assignee column stays empty; the channel post says "assign this
task to me" as a public claim.
Delegated mode is for handing work to a teammate who isn't you.
Anything else is broadcast.
1.2 "Assign to " → flow-slack orchestrates both writes
When the user says "assign this task to " or "delegate this
to " for a teammate who isn't them:
- Resolve name → email via
flow-slack find-user "<name>" (§2.1.a
covers the cases).
flow update task <slug> --assignee <resolved-email> ← flow's
side: records the assignee column. flow-slack does NOT touch
flow's DB itself; this skill drives that step.
- Draft a one-line task description from
tasks/<slug>/brief.md.
- AskUserQuestion to approve/edit/skip the post.
- On approve:
flow-slack assign <slug> <resolved-email> --summary "<draft>"
— short channel post + DM full brief to the assignee.
The point: a single "assign this to alice" utterance from the user
results in BOTH writes happening in the right order, end-to-end.
Don't stop after step 2 — the user's intent includes the Slack
delegation, that's why they engaged this skill.
1.3 "Pick this up from Slack" → origin mode
Detecting a flow-slack-format kickoff. If the inbox item came
from someone else's flow-slack assign to you, its text starts
with :pushpin: and contains the phrase "I just DM'd you the full
brief". Example:
:pushpin: :large_orange_circle: high • #auth • due May 24
<@YOU> — assigned to you. I just DM'd you the full brief. Updates land in this thread.
_<one-liner summary>_
There is no slug in the kickoff — slugs are local to each side.
The thread (channel + thread_ts) is the only cross-side bond.
When you see this format in flow-slack inbox show <id>:
-
The full brief is in 's DM to you on Slack.
AskUserQuestion:
This is a flow-slack assignment from <assigner>. Their channel
summary: "<one-liner>".
The full brief is in <assigner>'s DM to you on Slack. Pick a
local slug + tell me how to populate brief.md:
- Paste DM — give me a slug, I'll wait for you to paste the DM content
- Use channel summary — give me a slug, I'll stub brief.md from "<one-liner>"
-
On "Paste DM" → ask for slug, then wait for the user's paste,
then write the content to <FLOW_ROOT>/tasks/<slug>/brief.md.
-
On "Use channel summary" → ask for slug, then write the
one-liner to brief.md.
After brief.md is written, run:
flow add task "<name>" --slug <slug> # name = friendly version of slug
flow-slack inbox triage <id> <slug> # creates origin thread, links to assigner's thread
The origin thread points at the assigner's (channel, thread_ts), so
your flow-slack replys land in their thread — they see your
updates directly. Your close (when complete) posts the ✅ in their
thread, which their hook picks up via §2.2.a.
Slugs can diverge. Your local slug is independent — it's just
a handle for flow show task <slug>, flow-slack reply <slug>,
etc. The thread carries the actual task identity across both sides.
If the inbox text DOES NOT match the kickoff format (no :pushpin:
- "I just DM'd" phrase), fall through to the original §1.3 flow
below (this is a regular @mention you'll triage manually).
When the user converts a channel @mention into a flow task (either
explicitly — "make this a task" — or via the inbox triage prompt),
you must NOT post a kickoff in any channel. The mention already
has a Slack thread; we link the task to it.
The two-step dance:
flow add task "<name>" --slug <slug> (drafted from the
mention's text; ask for approval if non-obvious)
flow-slack inbox triage <id> <slug> — this records an
origin-mode thread row pointing at the mention's
(channel_id, thread_ts), with the mention author as
active_user_id. No Slack post is made.
After step 2, the task is "in" the original thread. Future updates
flow there via flow-slack reply <slug> "<text>". The existing
@assignee token in reply text now mentions the original asker,
which is usually what you want.
Acknowledgement post is optional. Ask:
Send a short ack reply in #project-vault?
Draft: "Got it, picking this up this week."
On send: flow-slack reply <slug> "<text>". On skip: nothing.
2. The proactive hook — handle it FIRST every turn
When you see a :inbox_tray: flow-slack — items pending for this turn. system reminder, address it before continuing with the
user's prompt. Two sections may appear:
2.1 Pending kickoff posts
The hook only surfaces tasks whose brief.md is non-empty —
tasks that are still being interviewed (brief unwritten or empty
placeholder) are excluded. So when you see a slug here, the
interview is done and it's safe to kick off.
## Pending kickoff posts
- oauth-budget (delegated, assignee: alice@acme.com)
- spec-cleanup (self, no assignee)
For each pending slug:
-
Read tasks/<slug>/brief.md from $FLOW_ROOT.
-
Draft a one-line task description — self-contained, real
task phrasing (not just the task name), aimed at the praxis bot.
-
Ask the user via AskUserQuestion:
Post `<slug>` to #flow-tasks?
<one-line context about who'll see what>
Draft summary: "<your draft>"
- Post — looks good
- Edit summary — let me tweak the one-liner
- Skip — don't post this one (won't ask again)
-
On answer:
- Post (delegated) →
flow-slack assign <slug> <email> --summary "<draft>"
- Post (self) →
flow-slack broadcast <slug> --summary "<draft>"
- Edit → ask for revised text; re-show AskUserQuestion
- Skip →
flow-slack decide <slug> post-on-create skipped
2.1.a Resolving the assignee — name → email
The hook reports the assignee value verbatim from flow's
assignee column. flow stores this as freeform text — it might
be alice@acme.com (email — usable directly), or alice (name —
NOT usable; flow-slack assign calls users.lookupByEmail and
will 400 on a bare name), or even something custom like @alice.
Before invoking flow-slack assign, always check whether the
assignee value contains @ and a .:
- Contains
@ + . (looks like an email) → use as-is.
- Anything else → resolve via
flow-slack find-user "<value>":
- One match → use the resolved email.
- Multiple → AskUserQuestion to pick.
- Zero matches → ask the user for the email; do not guess.
This is the same name-resolution flow used for user-initiated
delegation (§4) — apply it equally when acting on a hook prompt.
2.2 New replies in delegated threads
## New replies in delegated threads
### oauth-budget — 2 new replies
Thread: <permalink>
**[ts] Alice Chen**
Started; blocked on the bucket-store choice.
For each thread:
- Acknowledge briefly to the user in one sentence — "FYI Alice
replied: she's going with Redis unless you object."
- For each individual reply, apply the rubric below.
- Continue with whatever the user originally asked.
Reply-curation rubric
- Skip ingestion (don't write to flow): emoji-only, single-word
acks (
thx, k, 👍, received, got it).
- Ingest (write a flow note): progress markers (started /
blocked / ETA), decisions, questions to user, status milestones.
To ingest:
flow note <slug> "<author> <date>: <curated summary of the reply>"
flow-slack ingest <slug> <reply-ts>
The raw replies always live in flow-slack.db's reply table —
audit log is intact regardless of curation decisions.
2.2.a Detecting an assignee's close in the thread
When a reply matches the close pattern, the assignee has marked
the work done from their side. Pattern: the reply contains
:white_check_mark: AND any of these phrases:
:white_check_mark: and any of:
- "I've completed this task"
- "Closed — thanks"
- "Done. <@" (the new origin-mode close)
- "Done — " (alternative phrasing)
- "Done with this task"
- "Task closed" (legacy v2 format)
The thread is the source of truth. The reply was fetched
scoped to YOUR thread's (channel, thread_ts) pair, so any close
phrase in that scope IS the close for this task — regardless of
slug, mode, or who wrote it. Trust the thread.
The detection requires BOTH the checkmark emoji AND a phrase, on
the same reply. A :white_check_mark: alone (e.g. "thanks 👍") is
NOT a close. A phrase without the emoji ("I think I'm done") is
NOT a close — those are conversational, not the rendered close.
On detection:
-
Acknowledge the close to the user: "Alice closed
<slug> — here's what she shipped:
"
-
AskUserQuestion:
`<slug>` was closed by <assignee>. Sync your local flow task?
- Yes — mark done in flow + close my thread row
- Just thread — close my flow-slack thread row only (don't mark flow done yet)
- Leave alone — keep my local state open
-
Yes → flow mark done <slug> (flow CLI), then
flow-slack close <slug> --no-post (DB-only; assignee's ✅
already posted in the thread — don't double-post).
-
Just thread → flow-slack close <slug> --no-post. The local
flow task stays open for any followup the user wants to do.
-
Leave alone → nothing.
Never call flow-slack close <slug> WITHOUT --no-post in
response to an assignee's close — that posts another ✅ on top of
theirs. The --no-post flag is the mirror flag.
2.2.b On-demand status check
When the user asks "what's the status of <slug>?" / "any update
from ?" / "what's saying?", run
flow-slack new-replies <slug> to fetch the latest replies.
If there are new replies: summarize the substantive ones for
the user (skip ack-only / emoji-only ones per the §2.2 curation
rubric). If one matches the close pattern, follow §2.2.a.
If the output is empty: be conversational about it, then offer
to nudge. AskUserQuestion:
Nothing new from <assignee> in `<slug>` since {last_check_time}.
The last reply was {brief paraphrase of most recent reply, or
"the kickoff post" if no replies yet}.
- Nudge them — `flow-slack nudge <slug>` pings them in the thread
- Custom nudge — let me draft the ping text first
- Leave alone — check back later
On Nudge them → flow-slack nudge <slug> (uses the default
"any updates?" phrasing).
On Custom nudge → AskUserQuestion for the ping text, then
flow-slack nudge <slug> "<text>".
On Leave alone → acknowledge and stop.
Phrase status output naturally — "Alice hasn't posted since
Tuesday morning" beats "no new replies since last check." Use the
TS in the most recent reply to compute relative time when you
can.
2.3 Pending channel mentions
## Pending channel mentions (2)
### #project-vault — Alice • inbox id: 7
https://acme.slack.com/archives/...
> can you review the secrets rotation flow?
### #acme-shared [external] — Customer • inbox id: 8
> can you ship the patch this week?
For each item:
-
Read the mention. Run flow-slack inbox show <id> if you
need the un-truncated record.
-
Note [external] — those are Slack Connect / shared
channels. Customers and partners are higher-signal; default the
AskUserQuestion toward conversion.
-
AskUserQuestion:
Inbox: <@Alice> in #project-vault asked you to "review the
secrets rotation flow." How do you want to handle it?
- Convert to flow task — I'll draft a slug + name
- Dismiss — not a task, just FYI
- Leave pending — decide later
-
On answer:
- Convert → follow the §1.3 dance:
flow add task + flow-slack inbox triage. Then ask whether to post a short ack.
- Dismiss →
flow-slack inbox dismiss <id>.
- Leave pending → no action; resurfaces next prompt.
Triage one item at a time — different contexts, different judgment.
The user can flow-slack inbox list to see the whole queue.
Inbox anti-patterns
- Don't auto-create — always ask, even for external mentions
(default toward convert, but still ask).
- Don't silently dismiss without confirming.
- Don't run
flow-slack inbox poll inside a Claude turn — the
hook already polls. Manual poll is for CLI users.
3. Command surface
| What the user wants | What you run |
|---|
| Delegated kickoff | flow-slack assign <slug> <email> --summary "<one-liner>" --channel <C> |
| Same, with files | flow-slack assign <slug> <email> --summary "<one-liner>" --attach PATH --channel <C> |
| Self-broadcast kickoff | flow-slack broadcast <slug> --summary "<one-liner>" --channel <C> |
| Reassign (same thread) | flow-slack reassign <slug> <new-email> |
| Post into the active thread | flow-slack reply <slug> "<text>" |
| Reply with files | flow-slack reply <slug> "<text>" --attach PATH |
| Drop files in | flow-slack attach <slug> <path>... |
| Ping the assignee | flow-slack nudge <slug> |
| Get the latest thread state | flow-slack status <slug> |
| Fetch only new replies | flow-slack new-replies <slug> |
| Mark a reply as ingested | flow-slack ingest <slug> <reply-ts> |
| Record "don't ask" | flow-slack decide <slug> post-on-create skipped |
| Close the delegation | flow-slack close <slug> |
| Find a teammate by name | flow-slack find-user "<name>" |
| Inspect state (debug) | flow-slack show <slug> |
| List pending mentions | flow-slack inbox list |
| Show one mention | flow-slack inbox show <id> |
| Convert mention → flow task | flow add task ... + flow-slack inbox triage <id> <slug> |
| Dismiss mention | flow-slack inbox dismiss <id> |
| Manual inbox poll | flow-slack inbox poll |
| Channel for kickoff (REQUIRED) | --channel <C> on assign/broadcast |
| List known channels | flow-slack channels list |
| Remember a channel (id + name) | flow-slack channels remember <id> <name> |
| Pass channel name with kickoff | --channel-name <NAME> on assign/broadcast |
After a successful reassign, also run:
flow edit <slug> --assignee <new-email>
flow-slack does not touch flow's DB — the skill bridges the two.
Pinging the assignee inside a reply
Use the literal token @assignee in the reply text — flow-slack
rewrites it to the proper Slack mention (<@USERID>) using the
cached user_id on the active thread, so the recipient gets a real
push notification:
flow-slack reply oauth-budget "@assignee any ETA on the rate limits?"
Token rules: word-bounded, so @assignee matches but
email@assignee.com and @assignees do not. Substituted in both
delegated and self mode. If you need to mention a non-assignee,
resolve them via flow-slack find-user and paste the literal
<@USERID> yourself.
4. Resolving teammate names → emails
flow-slack assign / reassign need an email. The user
will say "assign to Omendra". Run flow-slack find-user "Omendra"
first. TSV output: email\treal_name\tdisplay_name\ttitle.
- One match → use silently with a one-line confirmation.
- Multiple matches → AskUserQuestion to pick.
- Zero matches → try a prefix variant; otherwise ask the user.
5. Self-mode update flow
When the user logs a flow update on a task in self-broadcast mode,
ask:
Share this update to #flow-tasks too?
Draft post: "<text>"
- Yes, post to channel and log to flow
- No, just log to flow
- Edit before posting
- Yes →
flow note <slug> "<text>" then flow-slack reply <slug> "<text>".
- No →
flow note <slug> "<text>" only.
- Edit → ask for revised post text, re-show.
In delegated mode, do NOT ask "post to thread?" — that thread
belongs to the assignee. Only call flow note. Post into the
thread only when the user explicitly asks ("tell Alice that …").
5b. Mirroring flow updates to a linked Slack thread
ANY task with an active thread row (delegated, self, or origin)
can have its flow updates mirrored to the linked Slack thread. When
the user logs a note via flow note <slug> or finishes a status
milestone, check whether a thread exists and offer to mirror.
The mode determines the default:
| Mode | Default | Reason |
|---|
self | Yes | The thread is your public work log; mirroring keeps it current. |
origin | Ask | The thread is someone else's; responding is usually wanted but not automatic. |
delegated | No | The thread belongs to the assignee; only mirror when explicitly relevant. |
Mechanism: flow-slack show <slug> returns the thread state as
JSON (includes mode + permalink). Check it before asking. Then:
flow-slack reply <slug> "<short brief>"
The reply verb posts a threaded message into the linked thread
using the stored (channel_id, thread_ts) — works the same for
all three modes.
In origin and self modes, the literal @assignee token in
reply text gets substituted to the stored active_user_id (the
mention author in origin mode, you in self mode). In delegated
mode, it pings the assignee.
6. Reassignment flow
- User: "reassign to Bob".
- Resolve Bob →
flow-slack find-user "Bob".
- One-line confirm: "Reassign
oauth-budget from Alice to Bob
(bob@acme.com)? Will post a handoff reply in the existing thread
and DM Bob the full brief."
- On confirm:
flow-slack reassign oauth-budget bob@acme.com
flow edit oauth-budget --assignee bob@acme.com
7. Close flow
Closing is mode-specific. The verb flow-slack close <slug> always
stamps closed_at in flow-slack.db and clears decision rows; the
Slack-side close message is governed by --no-post. Defaults vary
by mode:
7.1 Self mode
The user finished work they kicked off themselves. ALWAYS:
-
Draft a one-line completion brief. Pull from
flow show task <slug> notes or recent activity if available.
-
AskUserQuestion:
Close `<slug>`?
Brief: "<draft>"
- Post & close — looks good
- Edit — let me tweak the brief
- Skip — close without a Slack post
- Don't close — never mind
-
Post & close → flow-slack close <slug> --summary "<brief>"
-
Edit → ask for revised text; re-show AskUserQuestion
-
Skip → flow-slack close <slug> --no-post
-
Don't close → nothing
7.2 Origin mode
You picked this task up from someone else's @mention. The original
asker (active_user_id) is in the thread and gets <@>-pinged on
close. Same flow as self mode, including the brief draft. The
rendered message becomes "Done. <@asker> — closing the loop.
" — so the brief MUST be written for the asker's
audience, not internal notes.
7.3 Delegated mode (assigner side)
NEVER auto-close from your side. The assignee is doing the work;
they post their own close in the thread. Your job is to wait for
their close and mirror it locally. See §2.2 (close-pattern detection)
for that workflow.
If the user explicitly asks you to close a delegated task yourself
(e.g. "the work is done, mark it closed even though Alice hasn't
posted"), still draft a brief and AskUserQuestion, then run
flow-slack close <slug> --summary "<brief>". The render will say
"Closed — thanks <@alice>!" with the brief below.
8. Anti-patterns (skill rules)
- Don't post into a delegated thread without explicit user ask.
- Don't auto-curate replies that are emoji-only or single-word acks.
- Don't generate the one-liner without user approval before posting.
- Don't ask the post-on-create prompt twice for the same task — the
hook predicate filters this; trust it.
- Don't suppress flow-slack CLI errors — relay verbatim.
- Don't ask "post to channel?" on flow updates in delegated mode.
- Don't conflate
flow done with flow-slack close.
- Don't read flow-slack.db directly — go through the CLI verbs.
- Don't omit
--summary on assign/broadcast — the CLI will error.
9. Migration note (one-time)
If the user has pre-v2 slack.json sidecars from old assignments,
on first run flow-slack migrate --scan ~/.flow* populates the new
SQLite database. Idempotent — safe to re-run. Sidecar files are
left in place; user cleans up manually.
10. When NOT to use this skill
- General flow questions (no Slack intent) → use the
flow skill.
- General Slack messaging in other channels → flow-slack only
posts to the kickoff channel chosen per call.
- Direct teammate chats outside the flow scaffolding → just point
at Slack.