hermes - ✅(Solved) Fix Empty-response retry loop on claude-opus-4-7 in Slack group threads (non-@mention discussion messages) [1 pull requests]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

On claude-opus-4-7 (Anthropic provider) in Slack group threads, a genuine discussion message that is not an @mention of the bot can cause the model to reason into "don't reply" and emit no visible text. The gateway interprets that empty content as a generation failure and retries, producing the sequence:

  • Thinking-only response — nudging to continue (prefill-nudge retries)
  • Empty response from model — retrying
  • Final: Model produced reasoning but no visible response after all retries

The turn terminates with (empty) and the model cannot self-detect the loop, since the retry/nudge messages are system-injected and never surface in the model's own transcript on later turns.

Root Cause

Root Cause (hypothesis)

Fix Action

Fixed

PR fix notes

PR #13252: fix(gateway): honor group-chat silence in empty-response retry loop (#13248)

Description (problem / solution / changelog)

Fixes #13248

On claude-opus-4-7 in Slack group threads, a genuine discussion message that is not an @mention causes the model to reason into "don't reply" (per its group-chat rules) and emit no visible text. The gateway retry loop treated this as a generation failure: 2× thinking-only prefill-nudge → 3× empty-response retry → fallback-provider switch, ultimately posting a (empty) warning to the channel that fought the intended silence.

Fix

Plumb a silence_allowed signal from adapter → gateway → agent:

  • MessageEvent — new silence_allowed: bool = False field.
  • Slack adapter — sets silence_allowed=(not is_dm) and (not is_mentioned) on the constructed MessageEvent. Messages flowing through thread-continuity / existing-session gates without a direct address get the flag.
  • GatewayRunner._run_agent — new silence_allowed kwarg, forwarded to AIAgent.run_conversation.
  • AIAgent.run_conversation — stores self._silence_allowed.
  • Retry gates (run_agent.py ~L10894, L10930, L10949) — all three branches now also require not self._silence_allowed. When silence is valid, the model's first empty/thinking-only response terminates the turn instead of triggering retries.
  • Gateway outbound — when silence_allowed is set and the final response is (empty), suppress the "⚠️ The model returned no response…" sentinel and send nothing. DMs and @mentions retain the warning.

DMs and direct @mentions get unchanged retry semantics — silence is never valid when the bot was explicitly addressed.

Tests

tests/test_silence_allowed_retry_suppression.py (8 tests, passing):

  • MessageEvent dataclass accepts and defaults the flag correctly
  • run_conversation and _run_agent signatures expose the kwarg with default False
  • The three retry gates suppress when silence_allowed=True
  • Addressed messages (flag=False) still take all retry branches

Also verified tests/test_empty_model_fallback.py still passes (12/12).

Changed files

  • gateway/platforms/base.py (modified, +7/-0)
  • gateway/platforms/slack.py (modified, +6/-0)
  • gateway/run.py (modified, +51/-25)
  • run_agent.py (modified, +10/-3)
  • tests/test_silence_allowed_retry_suppression.py (added, +132/-0)

Code Example

# ~L10887
if _has_structured and self._thinking_prefill_retries < 2:
    # nudge: "(Please continue — that last turn had no visible response.)"

# ~L10934
if _truly_empty and (not _has_structured or _prefill_exhausted) and self._empty_content_retries < 3:
    # retry
RAW_BUFFERClick to expand / collapse

Summary

On claude-opus-4-7 (Anthropic provider) in Slack group threads, a genuine discussion message that is not an @mention of the bot can cause the model to reason into "don't reply" and emit no visible text. The gateway interprets that empty content as a generation failure and retries, producing the sequence:

  • Thinking-only response — nudging to continue (prefill-nudge retries)
  • Empty response from model — retrying
  • Final: Model produced reasoning but no visible response after all retries

The turn terminates with (empty) and the model cannot self-detect the loop, since the retry/nudge messages are system-injected and never surface in the model's own transcript on later turns.

Environment

  • Model: claude-opus-4-7 (provider: anthropic)
  • Surface: Slack group thread (not DM)
  • SOUL / system-prompt rules loaded: "speak less in groups", "silence means silence", "no pointless ack messages"
  • Relevant code: run_agent.py ~L10875–L11012 (thinking-only prefill + empty-response retry branches)

Reproduction

  1. Join a Slack group/channel that the bot is opted into via thread-session or free-response.
  2. Post a genuinely interesting discussion question addressed to the room, without @bot.
  3. Observe: either silence (intended) OR a 5-step empty-retry loop that still terminates with (empty).

Root Cause (hypothesis)

Two independently-correct behaviors compose badly:

  1. Prompt layer teaches the model that silence is a valid response in group chats when not @mentioned.
  2. Gateway layer (run_agent.py) treats "structured reasoning with no visible text" as a generation failure and retries up to 2× prefill-nudge + 3× empty-response retry + fallback provider switch.

When the model's chosen action is silence, the retry loop fights the intent instead of honoring it.

Context (run_agent.py)

# ~L10887
if _has_structured and self._thinking_prefill_retries < 2:
    # nudge: "(Please continue — that last turn had no visible response.)"

# ~L10934
if _truly_empty and (not _has_structured or _prefill_exhausted) and self._empty_content_retries < 3:
    # retry

Neither branch has a signal for "was the model actually addressed in this turn?"

Proposed Fixes

1. Gateway: plumb an is_addressed signal into the agent loop. Slack adapter already computes is_mentioned, reply_to_bot_thread, in_mentioned_thread, has_session. Pass a derived is_addressed boolean (true when this specific message mentions the bot, replies directly to it, or is in a DM) into AIAgent.run_conversation(). When is_addressed=False and the model returns thinking-only or empty on the first call, treat as intentional silence: skip the retry loop and return an empty final response that the adapter elides (no (empty) placeholder posted to the channel).

2. Model-side invariant (prompt). Add to SOUL/system: "When you choose silence in a group chat, produce an empty text response with no reasoning tokens spent on the reply; if you produce reasoning, you must produce visible text." This makes the two branches consistent but doesn't fully fix the loop if the model still emits reasoning.

3. Transcript surfacing. When the empty-retry loop terminates, append a short system-role note into the next turn's context: "Your previous turn in thread X returned empty after N retries — check your group-chat rules if this was unintentional." Cheap, diagnosable, and recovers agency for the model.

Fix (1) is the cleanest — it aligns gateway retry semantics with the already-computed addressing state.

Related

  • 2026-04-13: Anthropic adapter thinking-only blocks on mid-session model switch
  • Earlier today (2026-04-20): over-reinforced group silence causing internal no-reply

Filed by Kaito on Waseem's behalf.

extent analysis

TL;DR

The most likely fix is to plumb an is_addressed signal into the agent loop to align gateway retry semantics with the already-computed addressing state.

Guidance

  • Verify that the is_addressed signal is correctly computed by the Slack adapter, considering factors such as is_mentioned, reply_to_bot_thread, in_mentioned_thread, and has_session.
  • Implement the proposed fix (1) by passing the is_addressed boolean into AIAgent.run_conversation() and modifying the retry loop to treat intentional silence as a valid response when is_addressed=False.
  • Test the fix by reproducing the issue and observing whether the retry loop is skipped and an empty final response is returned without the (empty) placeholder.
  • Consider implementing the model-side invariant (prompt) proposed in fix (2) to ensure consistency between the model's behavior and the gateway's retry semantics.

Example

# Example of how to pass the is_addressed signal into AIAgent.run_conversation()
def run_conversation(self, ...):
    is_addressed = self.slack_adapter.is_addressed
    if is_addressed and self._thinking_prefill_retries < 2:
        # nudge: "(Please continue — that last turn had no visible response.)"
    elif not is_addressed and self._truly_empty:
        # treat as intentional silence and return empty final response
        return ""

Notes

The proposed fix assumes that the is_addressed signal can be accurately computed by the Slack adapter. If this is not the case, additional work may be required to improve the accuracy of this signal.

Recommendation

Apply workaround by implementing the proposed fix (1) to plumb an is_addressed signal into the agent loop, as it is the cleanest and most effective solution to align gateway retry semantics with the already-computed addressing state.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix Empty-response retry loop on claude-opus-4-7 in Slack group threads (non-@mention discussion messages) [1 pull requests]