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:
- 2×
Thinking-only response — nudging to continue (prefill-nudge retries)
- 3×
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
- Join a Slack group/channel that the bot is opted into via thread-session or free-response.
- Post a genuinely interesting discussion question addressed to the room, without
@bot.
- 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:
- Prompt layer teaches the model that silence is a valid response in group chats when not @mentioned.
- 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.
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 — retryingModel produced reasoning but no visible response after all retriesThe 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
claude-opus-4-7(provider:anthropic)run_agent.py~L10875–L11012 (thinking-only prefill + empty-response retry branches)Reproduction
@bot.(empty).Root Cause (hypothesis)
Two independently-correct behaviors compose badly:
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)Neither branch has a signal for "was the model actually addressed in this turn?"
Proposed Fixes
1. Gateway: plumb an
is_addressedsignal into the agent loop. Slack adapter already computesis_mentioned,reply_to_bot_thread,in_mentioned_thread,has_session. Pass a derivedis_addressedboolean (true when this specific message mentions the bot, replies directly to it, or is in a DM) intoAIAgent.run_conversation(). Whenis_addressed=Falseand 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
Filed by Kaito on Waseem's behalf.