Skip to content

fix(slack): clear assistant typing indicator after response delivery - #8414

Closed
lawrence3699 wants to merge 1 commit into
NousResearch:mainfrom
lawrence3699:fix/slack-stop-typing-clear-status
Closed

fix(slack): clear assistant typing indicator after response delivery#8414
lawrence3699 wants to merge 1 commit into
NousResearch:mainfrom
lawrence3699:fix/slack-stop-typing-clear-status

Conversation

@lawrence3699

Copy link
Copy Markdown

What does this PR do?

When MCP tools execute after the main text response is sent, Slack's assistant.threads.setStatus indicator ("is thinking...") can persist for up to 2 minutes instead of auto-clearing. This adds an explicit stop_typing() override to clear the status after all processing completes.

Related Issue

Fixes #8387

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • gateway/platforms/slack.py: Added _typing_thread_ts dict to track active thread timestamps, stored thread_ts during send_typing(), and added stop_typing() override that calls setStatus(status="") to explicitly clear the indicator.
  • tests/gateway/test_slack.py: Added 4 regression tests covering: normal clear, no-op without prior send_typing, idempotent double-call, and graceful API error handling.

How to Test

  1. Configure Hermes with a Slack bot using Assistant API events + an MCP server
  2. Send a message that triggers both a text response and post-reply MCP tool calls
  3. Verify the "is thinking..." indicator clears immediately after the response, not after Slack's ~2 min timeout

Unit tests:

pytest tests/gateway/test_slack.py::TestStopTyping -v

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: macOS 15 (unit tests only — no live Slack workspace)

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

When MCP tools execute after the main text response is sent, Slack's
assistant.threads.setStatus indicator ("is thinking...") can persist
for up to 2 minutes instead of auto-clearing.

Override stop_typing() in SlackAdapter to explicitly clear the status
via setStatus(status="") after all processing completes. The base
class cleanup code already calls stop_typing() in the finally block
of _process_message_background(), so this override is picked up
automatically.

Fixes NousResearch#8387
Copilot AI review requested due to automatic review settings April 12, 2026 14:33

Copilot AI 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.

Pull request overview

Fixes a Slack Assistant UX bug where the assistant.threads.setStatus (“is thinking…”) indicator can persist after the user-visible reply is delivered by explicitly clearing the status at the end of processing.

Changes:

  • Track the active Slack thread_ts used for assistant.threads.setStatus so it can be cleared later.
  • Add a Slack stop_typing() override that calls assistant.threads.setStatus(status="").
  • Add unit tests validating the new stop_typing() behavior (clear, no-op, idempotency, error handling).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
gateway/platforms/slack.py Tracks thread timestamp used for typing and explicitly clears Slack Assistant status via stop_typing().
tests/gateway/test_slack.py Adds regression tests for clearing the Slack Assistant typing/status indicator.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +361 to +362
self._typing_thread_ts[chat_id] = thread_ts

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

_typing_thread_ts is keyed only by chat_id (channel_id). BasePlatformAdapter can run multiple concurrent sessions in the same channel differentiated by source.thread_id (session_key includes thread_id), and each session starts its own _keep_typing loop that calls send_typing(chat_id, metadata={"thread_id": ...}). In that case, concurrent send_typing calls will overwrite this dict entry and stop_typing() may clear the wrong thread (or no-op for the correct one), leaving a stuck indicator or clearing an in-flight one. Consider keying the tracking by (chat_id, thread_ts) and/or updating the stop_typing call path to include thread metadata so the clear targets the correct thread.

Copilot uses AI. Check for mistakes.
# 1 call for send_typing + 1 call for first stop_typing = 2
assert adapter._app.client.assistant_threads_setStatus.call_count == 2

@pytest.mark.asyncio

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

The regression tests cover single-thread usage, but they don’t exercise the multi-thread-in-one-channel case where two different thread_ids can be active concurrently (a case Hermes supports via session_key including source.thread_id). Adding a test that simulates two send_typing() calls with different thread_id values for the same channel and verifies each corresponding stop_typing clears the right thread would help prevent indicator-clearing regressions once the implementation is updated to track per-thread state.

Suggested change
@pytest.mark.asyncio
@pytest.mark.asyncio
async def test_clears_matching_thread_when_multiple_threads_active(self, adapter):
adapter._app.client.assistant_threads_setStatus = AsyncMock()
await adapter.send_typing("C123", metadata={"thread_id": "ts1"})
await adapter.send_typing("C123", metadata={"thread_id": "ts2"})
await adapter.stop_typing("C123", metadata={"thread_id": "ts1"})
first_clear_calls = [
call
for call in adapter._app.client.assistant_threads_setStatus.await_args_list
if call.kwargs == {
"channel_id": "C123",
"thread_ts": "ts1",
"status": "",
}
]
assert len(first_clear_calls) == 1
second_thread_clear_calls = [
call
for call in adapter._app.client.assistant_threads_setStatus.await_args_list
if call.kwargs == {
"channel_id": "C123",
"thread_ts": "ts2",
"status": "",
}
]
assert second_thread_clear_calls == []
await adapter.stop_typing("C123", metadata={"thread_id": "ts2"})
second_thread_clear_calls = [
call
for call in adapter._app.client.assistant_threads_setStatus.await_args_list
if call.kwargs == {
"channel_id": "C123",
"thread_ts": "ts2",
"status": "",
}
]
assert len(second_thread_clear_calls) == 1
@pytest.mark.asyncio

Copilot uses AI. Check for mistakes.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists platform/slack Slack app adapter comp/gateway Gateway runner, session dispatch, delivery labels Apr 24, 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 type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slack: assistant.threads.setStatus persists after response when MCP tools execute post-reply

3 participants