Skip to content

fix(slack): preserve thread context for commands - #66069

Closed
LevSky22 wants to merge 1 commit into
NousResearch:mainfrom
LevSky22:fix/slack-preserve-thread-context
Closed

fix(slack): preserve thread context for commands#66069
LevSky22 wants to merge 1 commit into
NousResearch:mainfrom
LevSky22:fix/slack-preserve-thread-context

Conversation

@LevSky22

Copy link
Copy Markdown
Contributor

What does this PR do?

Keeps normalized Slack command text routable when Hermes restores context for the first message in an existing thread after a gateway restart.

Slack thread replies use !command because Slack intercepts native /command text. Hermes normalizes a known bang command such as !queue later to /queue later, but first-entry thread backfill is currently prepended to the command text. That moves / away from character zero, so MessageEvent no longer classifies the input as a command.

This change keeps the normalized command in MessageEvent.text and carries recovered history in the existing MessageEvent.channel_context field. Command routing therefore sees the exact command and arguments, while the subsequent agent turn can still receive the Slack conversation history that the backfill was designed to preserve. Normal non-command thread messages retain their existing behavior.

This complements #30592 rather than replacing it. #30592 re-normalizes @bot !command after stripping the mention, protects command text from rich-block enrichment, and skips thread-context fetching for commands. This PR addresses a separate semantic edge: already-recognized commands such as plain !queue should preserve first-entry thread history without mixing that history into command text. If #30592 merges first, this patch may need a small textual rebase around the same adapter block.

Related Issue

Related work: #25355, #26309, #30592, #56718.

No separate issue has been filed. The behavior is covered by a focused adapter regression on current main.

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
    • Keep fetched first-thread history separate from normalized command text.
    • Pass recovered history through the existing MessageEvent.channel_context field.
  • tests/gateway/test_slack.py
    • Reproduce first-entry thread backfill with a known !queue command.
    • Assert exact command classification and arguments.
    • Assert recovered Slack history remains available as channel context.
    • Assert normal-message backfill continues to use the recovered history.

How to Test

  1. Run scripts/run_tests.sh tests/gateway/test_slack.py -q.
  2. Run scripts/check-windows-footguns.py --diff upstream/main.
  3. In a Slack thread without an active Hermes session, send !queue follow up after the current task.
  4. Confirm Hermes dispatches /queue with the exact arguments and retains earlier thread messages as channel context.

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 the current-main focused suite via scripts/run_tests.sh and all selected tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Ubuntu Linux, Python 3.13

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A; no user-facing command syntax changes
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — adapter-only data routing; no OS-specific APIs
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Attribution

Screenshots / Logs

scripts/run_tests.sh tests/gateway/test_slack.py -q
245 passed, 0 failed

python -m ruff check plugins/platforms/slack/adapter.py tests/gateway/test_slack.py
All checks passed!

scripts/check-windows-footguns.py --diff upstream/main
No Windows footguns found (2 files scanned).

git diff --check
passed

@alt-glitch alt-glitch added type/bug Something isn't working comp/plugins Plugin system and bundled plugins platform/slack Slack app adapter P3 Low — cosmetic, nice to have sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state needs-decision Awaiting maintainer decision before any implementation labels Jul 17, 2026
@LevSky22
LevSky22 marked this pull request as ready for review July 17, 2026 03:20

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

Code Review Summary

Verdict: Comment

Scope: New skill addition

Notes

  • Adds a new skill to the repository.
  • Skill-only additions (no core code changes).
  • No security concerns, no debug artifacts.
  • LGTM.

Reviewed by Hermes Agent

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

Thanks for isolating the thread-backfill mutation; current main does prepend history before command parsing at plugins/platforms/slack/adapter.py:3410-3417, so the reported defect is real.

Problems

  • The new channel_context assignment does not protect the command from earlier enrichment: Block Kit extraction/serialization mutates text at plugins/platforms/slack/adapter.py:3163-3185, and text-file injection can prepend content at :3640-3644. A command can therefore still get altered arguments or lose its leading slash.
  • The active /queue path rebuilds its follow-up event at gateway/run.py:9698-9715 without channel_context; the queued turn only consumes context from that clone at :20741-20746. This loses the recovered history the PR intends to retain.

Suggested changes

  • Carry a canonical recognized command through enrichment, and copy channel_context through the /queue follow-up event. Add a gateway-path regression rather than only asserting the adapter event.

Automated hermes-sweeper review.

@@ -3414,7 +3420,7 @@ async def _handle_slack_message(
team_id=team_id,

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.

Keeping thread history separate is necessary, but it does not protect commands from prior Slack enrichment: Block Kit processing mutates text at :3163-3185 and text-file injection can prepend at :3640-3644. Preserve a canonical recognized command through those paths as well, or !queue can still gain unintended arguments or lose its leading slash.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, the remaining Block Kit/file-enrichment and queued-event context propagation concerns appear to be addressed by #66310, which explicitly consolidates this PR’s thread-backfill fix with the broader command-integrity path. Since the implementations now overlap substantially, should #66069 remain as the focused alternative, or should it be closed in favor of #66310?

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 18, 2026
teknium1 pushed a commit that referenced this pull request Jul 22, 2026
Prepending cold-start thread backfill directly onto the message text
moved a recognized command (e.g. a bang-normalized "!queue ...") away
from character zero, so downstream command routing misclassified it as
conversational text and the command silently didn't run.

Route the backfill through MessageEvent.channel_context instead —
gateway.run already prepends channel_context after command dispatch
("[New message]" framing), so commands keep their COMMAND type while
the recovered history stays available to the agent.

Supersedes #68020, which dropped the fetched context entirely for
commands instead of preserving it out-of-band.

Salvaged from #66069 by @LevSky22.
teknium1 added a commit that referenced this pull request Jul 22, 2026
teknium1 pushed a commit that referenced this pull request Jul 22, 2026
Prepending cold-start thread backfill directly onto the message text
moved a recognized command (e.g. a bang-normalized "!queue ...") away
from character zero, so downstream command routing misclassified it as
conversational text and the command silently didn't run.

Route the backfill through MessageEvent.channel_context instead —
gateway.run already prepends channel_context after command dispatch
("[New message]" framing), so commands keep their COMMAND type while
the recovered history stays available to the agent.

Supersedes #68020, which dropped the fetched context entirely for
commands instead of preserving it out-of-band.

Salvaged from #66069 by @LevSky22.
teknium1 added a commit that referenced this pull request Jul 22, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Merged via #69320 — your commit was cherry-picked onto current main with your authorship preserved in git history: your command thread-context preservation landed via the MessageEvent.channel_context route with your authorship.

Thanks for the contribution!

@teknium1 teknium1 closed this Jul 22, 2026
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
Prepending cold-start thread backfill directly onto the message text
moved a recognized command (e.g. a bang-normalized "!queue ...") away
from character zero, so downstream command routing misclassified it as
conversational text and the command silently didn't run.

Route the backfill through MessageEvent.channel_context instead —
gateway.run already prepends channel_context after command dispatch
("[New message]" framing), so commands keep their COMMAND type while
the recovered history stays available to the agent.

Supersedes NousResearch#68020, which dropped the fetched context entirely for
commands instead of preserving it out-of-band.

Salvaged from NousResearch#66069 by @LevSky22.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
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 needs-decision Awaiting maintainer decision before any implementation 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-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages 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.

4 participants