Skip to content

fix(slack): pass thread_ts through approval buttons and standalone sends - #9395

Closed
shivasymbl wants to merge 3 commits into
NousResearch:mainfrom
shivasymbl:fix/slack-thread-routing
Closed

fix(slack): pass thread_ts through approval buttons and standalone sends#9395
shivasymbl wants to merge 3 commits into
NousResearch:mainfrom
shivasymbl:fix/slack-thread-routing

Conversation

@shivasymbl

@shivasymbl shivasymbl commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Thread-aware interactive buttons for all Slack user-input prompts. Responses stay in-thread instead of creating new top-level messages.

  • Add thread_id to _send_slack() — the only platform missing it
  • Encode thread_ts in approval button values for thread propagation
  • New: send_update_prompt() — Yes/No Block Kit buttons for Slack (was Discord/Telegram only)
  • New: send_choice_prompt() — Generic multi-option prompt (up to 25 options)

Changes

File Change
tools/send_message_tool.py Add thread_id to _send_slack(), forward from _send_to_platform()
gateway/platforms/slack.py _encode/_decode_button_value helpers, thread_ts in approval buttons, new send_update_prompt(), new send_choice_prompt() with action handlers
gateway/run.py Wire send_update_prompt with metadata for thread context
tests/gateway/test_slack_approval_buttons.py 346 lines added — encoding tests, update prompt tests, choice prompt tests
tests/tools/test_send_message_tool.py 103 lines added — _send_slack thread_id tests

5 files changed, 865 insertions(+), 10 deletions(-)

New capabilities

Method Description
send_exec_approval() Fixed — thread_ts encoded in button values
send_update_prompt() New — Yes/No Block Kit buttons, thread-aware
send_choice_prompt() New — generic multi-option prompt, up to 25 choices, file-based resolution
_send_slack() Fixed — thread_ts for all standalone deliveries

send_choice_prompt is fully generic — cron confirmations, routing decisions, model picks, topic selection:

await adapter.send_choice_prompt(
    chat_id=channel,
    prompt="Which blog topic?",
    choices=[("Digital Labor ROI", "dl_roi"), ("Customer Zero", "cz")],
    session_key="choice-001",
    metadata={"thread_id": thread_ts}
)

Test plan

  • 38 targeted tests pass (0 failures)
  • Button value encoding round-trips with session keys containing :
  • Legacy button values (no ||) decode correctly — backward compatible
  • _send_slack includes thread_ts when provided, omits when None
  • Update prompt Yes/No buttons render and resolve correctly
  • Choice prompt with multiple options renders and resolves correctly
  • All handlers registered with Slack app action listener

Fixes #9394

🤖 Generated with Claude Code

@houko houko 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.

Good fix — properly encoding thread_ts in button values to preserve thread routing is important. Backward compatibility with split logic is a nice touch. 👍

@shivasymbl
shivasymbl force-pushed the fix/slack-thread-routing branch from 89fd387 to 9bf43b2 Compare April 14, 2026 05:52
shivasymbl and others added 2 commits April 14, 2026 20:30
…sentinel detection

Complete Slack interactivity overhaul:
- Thread routing for approval buttons, update prompt, choice prompt
- _send_slack() thread_id support for standalone deliveries
- send_update_prompt() Yes/No Block Kit buttons
- send_choice_prompt() generic multi-option prompt
- _handle_cron_action_button() for hermes_action_* patterns
- ---SLACK_BLOCKS--- sentinel detection in send() for cron job buttons
- Block Kit markdown block type for rendering (format_message only for fallback)

6 files changed, 963 insertions(+), 26 deletions(-)

Fixes NousResearch#9394

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P1: Fix _handle_cron_action_button MessageEvent construction — use
    _build_source() + proper MessageEvent fields instead of invalid
    keyword args that caused TypeError on every button click.

P2: Add blocks array to _send_slack() — standalone sends now use
    Block Kit markdown block type matching the gateway send() path.

P3: Sentinel detection only strips content after successful JSON
    parse — prevents message corruption if normal content contains
    the literal ---SLACK_BLOCKS--- string.

Also update 4 test assertions to expect raw markdown passthrough
(no longer pre-converted to mrkdwn before _send_slack).

204 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@shivasymbl
shivasymbl force-pushed the fix/slack-thread-routing branch from 9bf43b2 to 7115256 Compare April 14, 2026 15:24
P1: _build_source → build_source (correct method name on BasePlatformAdapter)
P1: Remove _gateway_update_pending cleanup from _handle_update_action — the
    pending map lives on GatewayRunner, not the adapter. The watcher clears
    it when it reads the .update_response file.
P2: Remove undefined _progress_metadata from _watch_update_progress — the
    variable exists in a different scope. Thread context is handled by
    send_update_prompt internally.

204 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery platform/slack Slack app adapter labels Apr 27, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the detailed Slack investigation. This is an automated hermes-sweeper review; the reported thread-routing behavior is now implemented on current main.

  • Commit 74e845c00 (fix(slack): pass thread_ts in standalone send_message tool path) fixed the standalone-send defect.
  • The current migrated sender preserves that behavior at plugins/platforms/slack/adapter.py:4351-4352, adding Slack thread_ts from thread_id.
  • Slack approval prompts already post in-thread at plugins/platforms/slack/adapter.py:3257-3318. Their click handler updates that same prompt and resolves the existing session at plugins/platforms/slack/adapter.py:3655-3682, so it has no separate follow-up message that needs thread data encoded in the button value.
  • The Slack adapter moved from gateway/platforms/slack.py to plugins/platforms/slack/adapter.py in 1a3806605, so the remaining PR implementation targets a superseded layout.

@teknium1 teknium1 closed this Jul 12, 2026
@teknium1 teknium1 added sweeper:implemented-on-main Sweeper: behavior already present on current main sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data 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 12, 2026
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 P2 Medium — degraded but workaround exists platform/slack Slack app adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:implemented-on-main Sweeper: behavior already present on current main 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 sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slack: approval buttons and standalone sends ignore thread context

4 participants