Skip to content

fix(slack): extract Block Kit and attachment text in thread context and mention detection - #69316

Merged
teknium1 merged 4 commits into
mainfrom
slack/c1-block-text
Jul 22, 2026
Merged

fix(slack): extract Block Kit and attachment text in thread context and mention detection#69316
teknium1 merged 4 commits into
mainfrom
slack/c1-block-text

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Slack messages from apps/bots (Grafana, Datadog, Alertmanager, CI) carry their content in Block Kit blocks or legacy attachments with empty top-level text — the adapter silently dropped that content in thread-context fetch and mention gating. This PR makes both paths read blocks and attachments.

Fixes #68138, #52387, #33469.

Changes

  • plugins/platforms/slack/adapter.py: _render_message_text(msg) — unified helper merging top-level text, rich-text blocks, section/header/context text, legacy attachment fields (pretext/title/text/fields/nested blocks/fallback), and button URLs; used by _fetch_thread_context and _fetch_thread_parent_text.
  • _slack_mention_detection_text(event) — recovers <@UID> mentions authored only inside non-quoted Block Kit blocks; applied to the allow_bots=mentions bot filter and the channel-routing is_mentioned check. Quoted/forwarded mentions deliberately do NOT count (response-hijack safety).
  • 25 regression tests (blocks-only parent, attachment-only alert, Block-Kit-only mention, quoted-mention rejection, URL surfacing).

Note: the live-message handler already extracted blocks/attachments on main — only thread-context, parent-text, and the mention gates were still broken.

Credits

Salvaged with authorship preserved: #29541 (@bpross, earliest), #61261 (@xcompass), #52390 (@Bartok9).
Supersedes #68160, #52404, #52219, #33493, #32022, #30765.

Validation

Check Result
scripts/run_tests.sh tests/gateway/ -q -k slack 507 passed, 0 failed
ruff on touched files clean
Re-verified post-rebase onto current main by parent session green

Infographic

slack-block-kit-text-extraction

Bartok9 and others added 4 commits July 22, 2026 05:04
Closes #52387

Slack messages can carry the bot @mention only inside Block Kit `blocks`
(a `rich_text` section with a `user` element), with the flat top-level
`text` containing just a fallback string. Both mention gates read only the
flat `text`:

- the `allow_bots: mentions` bot-message filter, and
- the `is_mentioned` channel router (`routing_text = original_text`)

so such messages were silently dropped — `allow_bots: mentions` was
effectively non-functional for Block-Kit senders, and the same blind spot
hit `require_mention` / `strict_mention`.

Add `_collect_slack_block_mentions()` (walks blocks, recovers `<@uid>`
tokens from non-quoted rich-text `user` elements) and
`_slack_mention_detection_text()` (flat text + recovered mentions). Both
gates now use the merged detection text. Mentions nested in
`rich_text_quote` are deliberately ignored, preserving the existing
contract that quoted/forwarded content can't trick the bot. Also emit a
debug line when a bot message is dropped so silent drops are diagnosable.

Tests: 4 new cases in tests/gateway/test_slack_mention.py (Block-Kit
mention recovered, flat-text passthrough, no-mention, quoted-mention
ignored). 275 slack tests pass.
`_fetch_thread_context` and `_fetch_thread_parent_text` only read each
message's plain `text` field, so messages posted by apps (Alertmanager,
Grafana, PagerDuty, CI bots) — which carry their content in legacy
`attachments` or Block Kit `blocks` with an empty `text` — were dropped
entirely. When such a message *starts* a thread (e.g. an alert), a bot
mentioned mid-thread to investigate sees an empty thread and can only ask
"what should I investigate?".

Fall back to the existing `_extract_text_from_slack_blocks` and a new
`_extract_text_from_slack_attachments` helper when `text` is empty, so
app-posted alerts and notifications are visible in fetched thread history.

Adds TestThreadContextAppMessages (attachment-only, blocks-only, and
empty-message cases).
Bot-posted alerts (Honeycomb, PagerDuty, Datadog, GitHub bot, etc.) carry
their actionable content — section text, button URLs — in Block Kit
blocks, while the plain text field holds only the alert title.
_fetch_thread_context and _fetch_thread_parent_text only read
msg.get('text'), so that content never reached the agent.

Add a _render_message_text helper that merges top-level text with
readable block content, section/header/context text, actionable URLs,
and (folded in from #61261 during conflict resolution) legacy
attachment fields, and use it for thread-context and parent-text
rendering.

Salvaged from #29541.
@teknium1 teknium1 added the platform/slack Slack app adapter label Jul 22, 2026
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

running on 5906b80

CI timings

CI timings · View job

Wall time 7m29s vs 7m46s (-3.6%). 9 job(s) slower, 11 faster,

  • Build&Test Docker image / build (arm64, ubuntu-24.04-arm, linux/arm64, type=gha,scope=docker-arm64, type=gha,mode=max,scope...: -21.0s
  • Python tests / Run tests slice 1/8: -17.0s
  • Python tests / Run tests slice 7/8: +11.0s
  • Python tests / Run tests slice 5/8: -10.0s
  • Python tests / Run tests slice 2/8: -10.0s

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/plugins Plugin system and bundled plugins sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 22, 2026
@teknium1
teknium1 merged commit 453d0ee into main Jul 22, 2026
40 checks passed
@teknium1
teknium1 deleted the slack/c1-block-text branch July 22, 2026 13:57
chenwei791129 added a commit to chenwei791129/hermes-agent that referenced this pull request Aug 16, 2026
…ments

NousResearch#52387 was fixed for the carrier Slack's WYSIWYG composer produces — a
rich_text tree with a structured `user` element. Two other carriers are
still dropped, so a bot that explicitly @-mentions the gateway stays
invisible to the allow_bots="mentions" gate (adapter.py:5329) and to
is_mentioned routing (adapter.py:5603).

Root cause, two independent gaps:

1. `_collect_slack_block_mentions` recurses only through
   ("elements", "element") and appends only for `type == "user"` nodes. A
   section/header/context block carries its content under "text" (a dict)
   or "fields" (a list), neither of which is walked; and a hand-built app
   writes the mention as a raw <@uid> substring, so there is no `user`
   node to match even once the subtree is entered.

2. Detection returns early when `event["blocks"]` is falsy and never
   consults `attachments`, so a mention living in an attachment field or
   in attachment-nested blocks is invisible. NousResearch#69316 added
   `_extract_text_from_slack_attachments` for exactly these apps
   (Alertmanager, Grafana, PagerDuty, CI) but applied it to display only.

The walker also descends "text"/"fields" and harvests raw tokens from
string values; a new `_collect_slack_attachment_mentions` covers the
legacy carrier including attachment-nested blocks. A shared
`_SLACK_USER_MENTION_RE` normalizes the labelled `<@u123|alice>` form to
the bare token the gates compare against, with a deliberately permissive
ID class: the gates substring-compare against whatever auth.test
returned, so a narrower class would silently drop the mention this
recovers. Extraction is factored into `_extract_mention_tokens`, so the
carve-outs below live in one place and apply to every carrier.

Recovered mentions are returned as a list by `_slack_recovered_mentions`
rather than spliced into the routing text. Splicing would corrupt the two
other consumers of that text: `_slack_message_addressed_to_other_user`
reads its *first* token — and with an empty top-level text (the alert-bot
shape this fixes) the appended tail becomes that token, so the message is
dropped as "addressed to someone else" — while user-configured wake-word
regexes are matched with `.search`, so an anchored pattern like
`^hey hermes$` stops matching the moment a tail is appended. The gates
now consume `_slack_event_mentions_bot` / `_slack_mention_gate_inputs`,
which keep the routing text byte-identical to `event["text"]` and report
recovered mentions through `is_mentioned` instead.

The quote carve-out from NousResearch#52390 is preserved and extended to the carriers
the structured `rich_text_quote` check cannot see:

  - a leading mrkdwn blockquote marker (`>` / the escaped `&gt;`) in any
    scanned string, so a peer app quoting an earlier request as context
    does not re-summon the bot;
  - attachments flagged `is_msg_unfurl` or `is_share`, mirroring the skip
    the agent-text path already performs at adapter.py:5483, so pasting a
    permalink to an old bot request does not wake it;
  - `fallback` is not scanned at all — Slack never renders it, so a
    mention living only there is invisible in the channel and notifies
    nobody.

Defensive handling is per-attachment rather than around the whole loop:
one malformed sibling now skips itself instead of discarding the genuine
mentions already collected, which would otherwise reintroduce the exact
silent drop this commit fixes.

The thread-parent wake check (NousResearch#24848) is migrated too. Its cached branch
returned the raw `msg["text"]` — empty for app-authored parents — so a
plain follow-up reply in an alert thread was dropped after a restart.

Tests: 28 new cases in tests/gateway/test_slack_mention.py (27 -> 55)
covering all seven carriers, the labelled form, dedupe, six malformed
payloads, each carve-out, and the two routing-text regressions. The
gating simulation and the new gate cases call the production predicates
directly, so the tests cannot pass while the real gate diverges. They
fail on unmodified main and pass with the fix. All 27
tests/gateway/test_slack*.py files pass (337 tests).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have platform/slack Slack app adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slack adapter: thread-context skips messages whose text lives in blocks/attachments (e.g. Grafana alerts)

5 participants