Skip to content

fix(slack): clear stuck assistant status on /stop - #32340

Closed
LeonSGP43 wants to merge 1 commit into
NousResearch:mainfrom
LeonSGP43:fix/32295-slack-stop-status
Closed

LeonSGP43 wants to merge 1 commit into
NousResearch:mainfrom
LeonSGP43:fix/32295-slack-stop-status

Conversation

@LeonSGP43

Copy link
Copy Markdown

Fixes #32295

Summary

  • clear Slack assistant thread status from /stop even when no running agent is registered
  • let SlackAdapter.stop_typing() clear a thread from explicit metadata when the in-memory chat_id -> thread_ts map is gone
  • add regression tests for the metadata fallback and the Slack /stop no-active path

Testing

  • uv run --frozen pytest -q -o addopts='' tests/gateway/test_slack.py -k 'stop_typing'
  • uv run --frozen pytest -q -o addopts='' tests/gateway/test_slack_stop_command.py
  • git diff --check
  • uv run --frozen ruff check gateway/platforms/slack.py gateway/run.py tests/gateway/test_slack.py tests/gateway/test_slack_stop_command.py

@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 May 26, 2026

@dhigginbotham dhigginbotham left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed by Daemon. Requesting changes: the new Slack status clearing logic does not appear to cover the main /stop fast paths.

Blocking findings:

  1. gateway/run.py:7036 — /stop while an agent is actively running is intercepted before normal command dispatch. That path calls _interrupt_and_clear_session(...) and returns an EphemeralReply, bypassing _handle_stop_command() where the new _clear_typing_indicator() helper lives. This can still leave Slack assistant/thread status stuck for the primary active-agent stop scenario.

  2. gateway/run.py:7268 — the pending-agent /stop fast path also releases state and returns directly, bypassing the new clearing logic. If send_typing() has already set Slack status during startup/pending state, this can also leave status uncleared.

Targeted tests run by reviewer: python -m pytest tests/gateway/test_slack.py::TestSendTyping tests/gateway/test_slack_stop_command.py -q => 12 passed.

Suggested fix: call the Slack typing/status clear helper (or equivalent adapter stop_typing) in the active-running and pending-agent /stop fast paths as well, and add regression tests for both paths.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for identifying the lost-map case. The issue is still present on current main, but the proposed placement does not reach the main /stop fast paths.

Problems

  • Current main intercepts active-agent /stop at gateway/run.py:9257 and pending-agent /stop at gateway/run.py:9519, bypassing the PR's _handle_stop_command() helper. The active path reaches BasePlatformAdapter.interrupt_session_activity() which calls stop_typing(chat_id) without metadata (gateway/platforms/base.py:3931-3939); that cannot use the proposed fallback when Slack's map is absent.
  • The Slack adapter has moved to plugins/platforms/slack/adapter.py:1544; /stop command handling is now in gateway/slash_commands.py:1054. This needs a port, not a clean application of the current diff.

Suggested changes

  • Port the metadata fallback to plugins/platforms/slack/adapter.py, then route metadata-aware clearing through normal, active, and pending /stop paths.
  • Add regressions for each of those paths with _active_status_threads absent.

Automated hermes-sweeper review.

Comment thread gateway/run.py
source = event.source
session_entry = self.session_store.get_or_create_session(source)
session_key = session_entry.session_key
adapter = self.adapters.get(source.platform)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This helper is only reached through normal command dispatch. /stop while an agent is active or pending is intercepted earlier and returns without reaching this method, so the primary stuck-status paths remain uncovered. Please route the same metadata-aware clear through those fast paths and add regressions.

@teknium1 teknium1 added the sweeper:blast-contained Sweeper blast radius: contained — one narrow path / opt-in / few users label Jul 13, 2026
teknium1 pushed a commit that referenced this pull request Jul 15, 2026
…tadata

Salvaged from #32340 by @LeonSGP43, adapted to the workspace-scoped
status tracking that landed in #63709:

- /stop with no running agent now best-effort clears the platform
  status indicator, so a phantom 'is thinking...' left by a gateway
  restart or a turn that died without a final send can always be
  dismissed (#32295).
- SlackAdapter.stop_typing clears an untracked thread when the caller
  names it explicitly in metadata — clearing an unset status is a
  harmless no-op on Slack's side. The fallback is skipped when multiple
  Slack Connect workspaces track the same channel+thread and no team_id
  is given, preserving #63709's cross-workspace safety guarantee.
@teknium1

Copy link
Copy Markdown
Collaborator

Merged via #64621 — your commit was adapted onto current main with your authorship preserved in git log (dbd870467).

The core of your fix survived intact: /stop with no running agent now clears the stuck assistant status, and stop_typing clears a thread named in explicit metadata even when the in-memory tracking map has no entry (the gateway-restart case from #32295). It's rebuilt on top of the workspace-scoped status tracking that landed in #63709, with one added guard: when multiple Slack Connect workspaces track the same channel+thread and no team ID is provided, the clear is skipped rather than guessed.

Thanks @LeonSGP43!

@teknium1 teknium1 closed this Jul 15, 2026
Gravezzz pushed a commit to Gravezzz/hermes-agent that referenced this pull request Jul 21, 2026
…tadata

Salvaged from NousResearch#32340 by @LeonSGP43, adapted to the workspace-scoped
status tracking that landed in NousResearch#63709:

- /stop with no running agent now best-effort clears the platform
  status indicator, so a phantom 'is thinking...' left by a gateway
  restart or a turn that died without a final send can always be
  dismissed (NousResearch#32295).
- SlackAdapter.stop_typing clears an untracked thread when the caller
  names it explicitly in metadata — clearing an unset status is a
  harmless no-op on Slack's side. The fallback is skipped when multiple
  Slack Connect workspaces track the same channel+thread and no team_id
  is given, preserving NousResearch#63709's cross-workspace safety guarantee.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…tadata

Salvaged from NousResearch#32340 by @LeonSGP43, adapted to the workspace-scoped
status tracking that landed in NousResearch#63709:

- /stop with no running agent now best-effort clears the platform
  status indicator, so a phantom 'is thinking...' left by a gateway
  restart or a turn that died without a final send can always be
  dismissed (NousResearch#32295).
- SlackAdapter.stop_typing clears an untracked thread when the caller
  names it explicitly in metadata — clearing an unset status is a
  harmless no-op on Slack's side. The fallback is skipped when multiple
  Slack Connect workspaces track the same channel+thread and no team_id
  is given, preserving NousResearch#63709's cross-workspace safety guarantee.
prmartinow pushed a commit to prmartinow/hermes-agent that referenced this pull request Aug 26, 2026
…tadata

Salvaged from NousResearch#32340 by @LeonSGP43, adapted to the workspace-scoped
status tracking that landed in NousResearch#63709:

- /stop with no running agent now best-effort clears the platform
  status indicator, so a phantom 'is thinking...' left by a gateway
  restart or a turn that died without a final send can always be
  dismissed (NousResearch#32295).
- SlackAdapter.stop_typing clears an untracked thread when the caller
  names it explicitly in metadata — clearing an unset status is a
  harmless no-op on Slack's side. The fallback is skipped when multiple
  Slack Connect workspaces track the same channel+thread and no team_id
  is given, preserving NousResearch#63709's cross-workspace safety guarantee.
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
…tadata

Salvaged from NousResearch#32340 by @LeonSGP43, adapted to the workspace-scoped
status tracking that landed in NousResearch#63709:

- /stop with no running agent now best-effort clears the platform
  status indicator, so a phantom 'is thinking...' left by a gateway
  restart or a turn that died without a final send can always be
  dismissed (NousResearch#32295).
- SlackAdapter.stop_typing clears an untracked thread when the caller
  names it explicitly in metadata — clearing an unset status is a
  harmless no-op on Slack's side. The fallback is skipped when multiple
  Slack Connect workspaces track the same channel+thread and no team_id
  is given, preserving NousResearch#63709's cross-workspace safety guarantee.
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-contained Sweeper blast radius: contained — one narrow path / opt-in / few users type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Slack: "is thinking..." status indicator gets stuck when agent ends without sending a reply

4 participants