Uh oh!
There was an error while loading. Please reload this page.
Deliver notifications through Slack - #405
Open
alex-clickhouse wants to merge 7 commits into
Open
Conversation
This was referenced Aug 23, 2026
alex-clickhouse
marked this pull request as ready for review
August 24, 2026 07:07
alex-clickhouseforce-pushed
the
alex/slack-channel-notifications
branch
9 times, most recently
from
August 25, 2026 10:59
eca2a10 to
fa558a9Comparealex-clickhouseforce-pushed
the
alex/slack-channel-notifications
branch
from
August 25, 2026 13:48
fa558a9 to
b04eda5CompareFan out questions and approvals as Block Kit cards, persist Slack delivery identifiers, attribute answers to the workspace member who acted, and keep expiry and re-delivery behavior consistent with the existing channels.
The existing-database upgrade test stamps the schema at the highest non-Slack migration, so it already fails if the Slack migration is not strictly newer. Keep the behavioral upgrade assertion and drop the weaker structural duplicate.
Three faults that between them meant a card either never appeared or stopped taking answers. notifications.channels defaulted to web and telegram, so nothing reached Slack until an operator found a key documented in one sentence with no default and no example. Add slack to the default, document the key, and put a notifications block in the example. README said delivery already covered Slack, which only held once that key was set by hand. A press carried the message's thread_ts into the delivery target. Slack fills thread_ts in on any message that has replies, so one reply under a card made every later press miss the record that post_notification wrote against the bare conversation, and each one answered "already answered or expired" while the row stayed pending until it expired. Look the record up by the conversation, which is where the card is posted. A reaction on a card routed to slack:<channel>. Shared channels have no conversation-wide session and the pickers do not list one, so an emoji opened a session that /nerve stop could never reach. Require a thread outside DMs, where one conversation is the session. Slack now being on by default, an absent channel is the ordinary case and logs at debug; a registered one that cannot take traffic still warns. A channel name nothing delivers to no longer passes in silence.
alex-clickhouseforce-pushed
the
alex/slack-channel-notifications
branch
from
August 25, 2026 14:43
b04eda5 to
bb41314Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 3 of the Slack channel stack. Sends the agent's questions and approval
requests to Slack as cards with buttons, and records the answers, using the
delivery records from #404 and the channel from #403.
Stack
What a person sees
When the agent calls
notify,ask_user, orpropose_action, Slack getsa Block Kit card. Questions and approvals carry one button per option. Pressing
one records the answer and settles the card in place, naming who pressed it.
When a question expires unanswered, the card is edited to say so and its
buttons are removed.
Slack is in the default delivery list, alongside the web UI and Telegram. A
transport that is not running is skipped, so this costs nothing until Slack is
configured.
Where a card goes
notifications.slack_channel_idnames the conversation. Without it, the firstliteral channel id in
slack.allow_channelsis used. Names and globs are notresolved into ids, because guessing wrong would post agent output somewhere
nobody asked for. With no usable id, delivery is skipped and says why rather
than reporting a card nobody saw as delivered.
How an answer finds its notification
A card is posted at conversation level, never inside a thread, so the delivery
record for it is keyed on the bare conversation. A button press is looked up
the same way.
This matters because Slack fills in
thread_tson any message that hasreplies. Reading that field from the press payload would stop matching the
record as soon as somebody replied under the card, and every later press would
report the notification as already answered while the row sat pending until it
expired.
For the same reason, a reaction on a card does not open a session. Shared
channels have no conversation-wide session, only threads, so a reaction on a
channel-level message has no session to join. Opening one would write a
slack:<channel>key that the session pickers deliberately do not list, andnothing could stop it afterwards.
What is recorded
(channel, target, message_id)deliveryrecords. No Slack-specific columns.
conversation with no record for that notification is refused.
broadcast to the web UI, and in the approval audit record.
answered_byalone would only say "slack".
target change and across re-delivery cycles.
Boundaries
The notification service owns delivery records and answer state. The Slack
adapter owns which conversation a card goes to, the Block Kit, and the Web API
calls.