Repository navigation
Agent posts an extra reply in the source chat after a tool already delivered the visible output #170147
Description
Summary
After an agent turn delivers its visible output through a tool (for example a message-tool send to another chat), the source chat can still get an extra, unwanted reply. Three forms were observed:
- The canned fallback "The tool run finished, but no final summary was produced. I did not repeat any completed actions." (
src/agents/embedded-agent-runner/run/settled-turn-finalization.ts). - With Anthropic models, a model reply to the injected runtime context: "I've received the runtime context. What would you like me to do?"
- A final that starts with
NO_REPLYbut has trailing text (for exampleNO_REPLY\nREADY) shows the trailing text.
Steps to reproduce
- In Telegram topic A, ask the agent to send a message to topic B with the message tool, and to reply
NO_REPLYin A. - B receives the message. A also gets one of the extra replies above.
Observed on the Telegram Test Server with Anthropic Haiku 4.5 and 5.5 (forms 1 and 2), and on Discord with OpenAI (form 3). The same fallback was seen on current main with Anthropic, so it is not caused by the transcript-owner work in #169438.
Expected behavior
When the model explicitly chooses silence after a confirmed visible send, an external source chat gets no extra message. Existing protections stay: an empty final after tool work still gets the fallback or recovery; mixed NO_REPLY + text is still delivered; WebChat, Control UI and subagent sessions still must return a result.
Causes found
- Fallback and outer required response: the settled-turn finalization and the auto-reply completion layer do not know that the last tool batch already confirmed a visible send to another target, so they treat an authored
NO_REPLYas a missing answer. Relevant owners:settled-turn-finalization.ts,incomplete-turn-classification.ts,incomplete-turn-recovery.ts,terminal-resolution.ts,agent-runner-result-payloads.ts,followup-delivery.ts. History: fix(agent): always reply after settled tool work #133520 (always reply after settled tool work), fix(auto-reply): surface empty message-tool-only completions #105765 / fix(auto-reply): surface empty interactive completions #100456 (surface empty interactive completions), fix(agents): Don't show an error message if the agent returns NO_REPLY #149642 (explicitNO_REPLYafter a reaction). - Anthropic runtime-context carrier: for Anthropic, the post-tool continuation ends with
assistant(tool_use), user(tool_result), user(runtime context). OpenAI's does not have the trailing runtime-context user turn. The trailing synthetic user turn invites the model to answer it. Seesrc/agents/embedded-agent-runner/run/provider-review-continuation.ts.
Design notes for a fix
A prototype (closed draft #170129, carrier split #170140) showed these constraints:
- Honor only a token-only
NO_REPLYafter a confirmed last-batch send, and only in external message-channel source sessions. - Do not treat an empty final as intentional silence.
- Do not suppress mixed
NO_REPLY+ text. - Merge the Anthropic runtime-context carrier into the tool-result user turn without changing the cached prompt prefix.
Impact
A confusing extra message in the source chat after a successful cross-chat send. Also seen live on a production install with the canned fallback.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
clawsweeper commentedon Oct 12, 2026
Codex review: this still needs some work.
Summary
Keep this issue open: current main retains the reported completion conflict, and the collaborator author explicitly deferred repair while preserving this issue as its tracker.
Reproducibility: Current source establishes the required-reply recovery path and separate Anthropic runtime-context carrier; Telegram and Discord observations are reporter evidence, with no independent live reproduction performed in this review.
Root-cause cluster
Relationship:
canonicalCanonical: #170147
Summary: The author consolidated both closed repair prototypes into this issue; destination transcript recording remains separate work.
Members:
superseded: fix: keep tool-delivered turns silent in the source chat #170129 - The author closed the incomplete combined prototype and transferred its analysis here.superseded: fix(anthropic): avoid treating runtime context as a new user turn #170140 - The author closed the Anthropic carrier split and transferred its constraints here.adjacent_distinct: fix: record delivered messages in destination conversations #169438 - That open PR owns destination transcript recording, while this issue concerns source completion and provider request shaping.Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.
Next step
Retain this issue under the author's explicit repair deferral; future work needs a failing cross-chat regression covering both terminal finalization and outer reply completion.
Review details
Best possible solution:
When repair resumes, carry confirmed last-batch delivery and explicit silence through both completion layers while correcting Anthropic carrier placement.
Do we have a high-confidence way to reproduce the issue?
Current source establishes the required-reply recovery path and separate Anthropic runtime-context carrier; Telegram and Discord observations are reporter evidence, with no independent live reproduction performed in this review.
Is this the best way to solve the issue?
The report's narrowed constraints are appropriate, but the closed prototype is incomplete: the repair must cover outer completion too, preserve empty-final recovery, and continue delivering mixed NO_REPLY plus substantive text.
AGENTS.md: found and applied where relevant.
Remaining risk / open question:
Codex review notes: model internal, reasoning medium; reviewed against 51211ac26ca0.
Label changes
Label changes:
P2: The requested delivery succeeds, but the source conversation receives a confusing additional response.impact:ux-friction: An unwanted fallback or runtime-context acknowledgment adds noise after successful tool delivery.issue-rating: 🦞 diamond lobster: Current issue advisory state selects this label.clawsweeper:source-repro: Current issue advisory state selects this label.Label justifications:
P2: The requested delivery succeeds, but the source conversation receives a confusing additional response.impact:ux-friction: An unwanted fallback or runtime-context acknowledgment adds noise after successful tool delivery.Evidence reviewed
What I checked:
src/agents/embedded-agent-runner/delivery-evidence.ts:95, 51211ac26ca0)src/agents/reply-completion.ts:55, 51211ac26ca0)src/auto-reply/reply/agent-runner-result-payloads.ts:170, 51211ac26ca0)packages/ai/src/transports/anthropic-messages.ts:239, 51211ac26ca0)docs/concepts/messages.md:197, 51211ac26ca0)How this review workflow works
@clawsweeper re-reviewcomments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.@clawsweeper re-reviewor@clawsweeper re-runon an open PR or issue to request a fresh review only.@clawsweeper reviewto request a fresh review only.@clawsweeper explainto ask for more context, or@clawsweeper stopto stop active automation.Reviewed October 12, 2026, 3:20 PM ET / 19:20 UTC.
pelleeee commentedon Oct 12, 2026
Another variant of this, on WhatsApp direct chats with 2026.10.1 (code unchanged in 2026.10.2): a
messagereacton the current inbound message, followed by a token-onlyNO_REPLY.Setup
agent:<id>:whatsapp:direct:<peer>), soresolveSourceReplyExpectation→"required"(silentReplyonly applies to groups).NO_REPLY.messagethrough thetool_callwrapper.What happens
messagewith{"action":"react","emoji":"🌸"}, withoutmessageId(so it targets the current message) and withoutfinal.Sent reaction "🌸" -> message …, tool result{"ok":true,"added":"🌸"}).NO_REPLY.settled post-tool turn lacked a final answer … running isolated finalization. Both finalization attempts returnNO_REPLYagain, thenusing terminal fallback reply.Why
resolveDeliveredCurrentSourceReplyonly counts a reaction as a source reply whensourceReplyFinal === true. In the message tool,requestedSourceReplyFinalisundefinedunless the model passesfinalexplicitly. So the delivery state stays"missing", and the explicitNO_REPLYis ignored because the expectation is"required".The
finalschema description says to set it totrueonly when the reaction is the complete response. That is our case, but in practice smaller models don't set an optional boolean reliably. Here the model never mentioned it in its reasoning, even with an explicit instruction in AGENTS.md, probably because it copied earlier reaction calls from the transcript. It also sees the tool only through the compacttool_callsurface.Suggestion (fits the fix constraints in this issue)
If the last tool batch contains a confirmed, non-removing
reacton the current source message, and the assistant's final text is a token-onlyNO_REPLY, treat the turn as answered or silent:A reaction plus an explicit silence token is unambiguous user-visible closure. Turns where the model reacts and then writes text are unaffected.
Local workaround (for reference only, not proposed as-is because it changes the default meaning of
finalfor everyone): in the message tool,After that the reaction counts as the delivered source reply, the turn ends right after the tool call, and no fallback text is sent. Verified in production on WhatsApp DMs.
Side note (see also #144118): the fallback string is hard-coded English and is delivered to end users in non-English chats. An option to suppress it for channel sessions would help a lot.