Skip to content

fix(slack): bridge rich_blocks opt-in from a top-level slack: block to extra - #58691

Open
kamonspecial wants to merge 1 commit into
NousResearch:mainfrom
kamonspecial:fix/slack-rich-blocks-config-bridge
Open

kamonspecial wants to merge 1 commit into
NousResearch:mainfrom
kamonspecial:fix/slack-rich-blocks-config-bridge

Conversation

@kamonspecial

Copy link
Copy Markdown
Contributor

What does this PR do?

SlackAdapter._rich_blocks_enabled() reads the Block Kit opt-in from
self.config.extra["rich_blocks"]. But under a top-level slack: block
the surface where every sibling key lives (require_mention, allow_bots,
free_response_channels, …) — that key never reached extra:

  • it is not one of the shared keys the loader bridges,
  • it is not env-bridged (unlike require_mention et al., which
    _apply_yaml_config translates into SLACK_* env vars), and
  • _merge_platform_map only deep-merges the nested extra sub-dict for blocks
    under platforms: / gateway.platforms:, not for a top-level slack: block.

So slack.rich_blocks / slack.extra.rich_blocks under a top-level slack:
block was silently inert: _rich_blocks_enabled() stayed False,
_maybe_blocks() returned None, and outbound agent messages went out as flat
mrkdwn text regardless of config. The Block Kit rendering that already exists
in the adapter (headers, dividers, rich_text lists, and native table
blocks) was unreachable from the top-level block — most visibly with markdown
tables, since Slack has no table syntax and | ... | renders as raw pipes.

Fix: have the slack _apply_yaml_config hook return the rich_blocks
value so the gateway merges it into PlatformConfig.extra via the returned-dict
dispatch the hook contract already provides (extra.update(seeded)). This
mirrors telegram's hook, which returns extras or None for the same reason.
Keeping the bridge in the slack plugin — rather than adding a gateway/config.py
whitelist entry — is the point of the plugin apply_yaml_config_fn migration
(#24849).

  • Both the flat slack.rich_blocks and the nested slack.extra.rich_blocks
    forms are accepted; the flat form wins when both are set, matching the
    discord/telegram hooks' flat-over-nested precedence.
  • Value coercion stays the reader's job (_rich_blocks_enabled() already
    handles true/1/on/…).
  • Env-only keys are untouched, and the hook still returns None when no
    rich_blocks is present — no behaviour change for existing configs.

(platforms.slack.extra.rich_blocks already worked, because _merge_platform_map
deep-merges the nested extra sub-dict for platforms:-nested blocks. This
change makes the top-level slack: forms behave the same as that documented
path.)

Related Issue

No existing issue — self-contained bug fix; root cause and repro are described
above. (Searched open issues/PRs for the config-bridge gap; none found.)

Complementary to the open rendering-side PR #56618 (harden rich table block
fallback
), which hardens block_kit.py / the fallback path against
invalid_blocks. This PR is orthogonal: it fixes the config→extra wiring so
the opt-in is reachable in the first place, and does not touch
_apply_yaml_config in #56618 (no overlap there).

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • plugins/platforms/slack/adapter.py_apply_yaml_config now returns
    {"rich_blocks": <value>} (was return None) so the gateway merges it into
    PlatformConfig.extra; accepts flat slack.rich_blocks and nested
    slack.extra.rich_blocks, flat wins. Updated the _rich_blocks_enabled and
    _apply_yaml_config docstrings and the register() comment to reflect the
    bridged surface.
  • tests/gateway/test_slack_block_kit_adapter.py — new
    TestApplyYamlConfigRichBlocksBridge (flat, nested, string, false,
    flat-wins-over-nested precedence, non-dict extra, absent, hook→reader
    roundtrip).
  • tests/gateway/test_config.py — end-to-end load_gateway_config() dispatch
    tests for both top-level forms reaching
    config.platforms[Platform.SLACK].extra["rich_blocks"].

How to Test

Repro (before this change): in config.yaml, under a top-level slack:
block, set extra: { rich_blocks: true }; ask the agent to reply with a
markdown table. It posts as raw | ... | pipes (no table block), because
extra["rich_blocks"] is never populated.

With this change: the same config posts a native Block Kit table block.

  1. pytest tests/gateway/test_slack_block_kit_adapter.py tests/gateway/test_config.py -q
  2. New unit + e2e tests fail against the pre-fix return None (slack isn't even
    seeded into config.platforms, so the e2e assertions raise KeyError),
    confirming they're real regression guards.
  3. Verified live against a running gateway: with slack.extra.rich_blocks: true
    under a top-level slack: block, a 5-column markdown table posts as a native
    table block, no invalid_blocks errors.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(slack): …)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run the affected suites — test_slack_block_kit_adapter.py,
    test_config.py, test_platform_registry.py: 146/146 pass. (The full
    tests/gateway/ run has ~10 unrelated timing/environment-flaky failures that
    also fail on the base commit; none are in files this PR touches.)
  • I've added tests for my changes
  • I've tested on my platform: macOS (Darwin 25.4.0)

Documentation & Housekeeping

  • I've updated relevant docstrings — updated _rich_blocks_enabled /
    _apply_yaml_config docstrings and the register() comment
  • I've updated cli-config.yaml.example if I added/changed config keys —
    N/A (rich_blocks is a pre-existing documented key; no new key introduced)
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture — N/A
  • I've considered cross-platform impact — N/A (pure config/dict handling, no
    file/process/terminal I/O)
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery platform/slack Slack app adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 5, 2026
@kamonspecial kamonspecial reopened this Jul 5, 2026
@kamonspecial

Copy link
Copy Markdown
Contributor Author

The failing check-attribution is addressed, and I've rebased onto latest main to clear the merge conflict. Everything else was green on the prior run. Could a maintainer approve the workflow run / take a look when convenient? Thanks!

@teknium1

Copy link
Copy Markdown
Collaborator

Thanks for the focused Slack configuration bridge. The premise is confirmed on current main: SlackAdapter._rich_blocks_enabled() reads only self.config.extra["rich_blocks"] (plugins/platforms/slack/adapter.py:2003), while Slack's current YAML hook returns None (plugins/platforms/slack/adapter.py:5059-5093). load_gateway_config() already merges non-empty hook returns into the platform extra mapping (gateway/config.py:1278-1304), so the proposed returned dictionary uses the intended plugin-owned extension contract.

The canonical platforms.slack.extra.rich_blocks path remains preserved by _merge_platform_map (gateway/config.py:1114-1133), and the proposed top-level precedence handling is localized to Slack.

This is an automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
@kamonspecial
kamonspecial force-pushed the fix/slack-rich-blocks-config-bridge branch 2 times, most recently from 54ebaec to 08e18ef Compare July 25, 2026 10:57
@kamonspecial

Copy link
Copy Markdown
Contributor Author

Rebased onto current main — the conflict is cleared, and the stale AUTHOR_MAP commit is dropped now that contributors/emails/kamon@gao-ai.com covers the mapping. Only the fix and its tests remain.

Two notes on scope:

  1. markdown_blocks and feedback_buttons (both added after this PR was opened) are read from config.extra the same way and are inert under a top-level slack: block for the identical reason — e.g. slack: {markdown_blocks: true} yields extra == {} even with tokens set. I kept this PR to rich_blocks to match its title and description, but I'm happy to extend it to bridge all three here, or open a follow-up — whichever you prefer.

  2. Per the existing hook-dispatch contract (extra.update(seeded) in load_gateway_config), a top-level slack.rich_blocks takes precedence over platforms.slack.extra.rich_blocks, the same way the telegram and discord hooks behave. When the top-level key is absent the hook returns None, so a nested value is left untouched.

@GottZ GottZ left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

Summary

Two PRs are listed, but they address distinct Slack defects: #58691 bridges the top-level rich_blocks opt-in into PlatformConfig.extra, while #64267 classifies transient chat.update transport failures as retryable and preserves progress-message identity across normal and overflow edits.

Related pull requests

  • #58691 related — (+132/-9) — merge: The diff directly fixes the reported configuration-path mismatch by returning rich_blocks from Slack’s YAML hook for merging into PlatformConfig.extra, with coverage for flat and nested top-level forms, precedence, false values, malformed extra data, and the adapter reader.
  • #64267 [closed] related — (+389/-30) — already integrated, not a duplicate: This closed PR addresses an unrelated Slack progress-edit failure mode by making transient transport failures retryable. Despite the earlier keep_open review on #64267, the shown diff explicitly fixes its blocking overflow-path concern by retaining can_edit on retryable failures and adds an overflow regression; the contributor confirms that the implementation was merged via #70189.

Suggested consolidation

Merge #58691 as the focused fix for the inert top-level Slack rich_blocks configuration path. #64267 should remain closed because its separate network-retry fix was merged via #70189; no listed PR should be closed as a duplicate of #58691.

Cross-PR triage: Reviewed 2 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 31 kB of PR diffs, 11 kB of issue/PR text, 4 kB of discussion (6 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

…o extra

SlackAdapter._rich_blocks_enabled() reads the opt-in from
self.config.extra["rich_blocks"], but under a top-level `slack:` block that key
never reached extra: it is not a shared key, is not env-bridged, and
_merge_platform_map only deep-merges the nested `extra` sub-dict for
`platforms:`-nested blocks. So `slack.rich_blocks` / `slack.extra.rich_blocks`
under the top-level `slack:` block — where every sibling key (require_mention,
allow_bots, …) lives — was silently inert, and Block Kit rendering (headers,
dividers, rich_text lists, native `table` blocks) stayed off there.
(`platforms.slack.extra.rich_blocks` already worked via _merge_platform_map.)

Have the slack _apply_yaml_config hook return the rich_blocks value so the
gateway merges it into PlatformConfig.extra — the same returned-dict dispatch
telegram's hook uses (`extras or None` -> extra.update()). The flat
`slack.rich_blocks` wins over the nested `slack.extra.rich_blocks`, matching the
discord/telegram hooks' flat-over-nested precedence. Coercion stays the reader's
job; env-only keys and the return-None-when-absent contract are unchanged.

Adds unit tests for the hook (flat/nested/string/false/precedence/non-dict) and
end-to-end load_gateway_config dispatch tests for both top-level forms.
@kamonspecial
kamonspecial force-pushed the fix/slack-rich-blocks-config-bridge branch from 08e18ef to 2a0cc70 Compare August 13, 2026 14:31
@kamonspecial

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (fa83af3) — the conflict is cleared and the branch is mergeable again.
The premise still holds on main: _rich_blocks_enabled() reads only config.extra, and the
YAML hook still returns None. All 10 tests in the PR pass locally, plus the full slack-related
gateway suite (462 passed). Could a maintainer approve the workflow run when convenient? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have platform/slack Slack app adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades 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.

4 participants