You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Bug]: sessions_yield resume reply recorded but not delivered; auto-announce direct-text mirror delivered instead → user gets child raw summary, not parent reply #90944
Runtime / message delivery (data loss — user-visible message lost)
Beta release blocker
No
Summary
When a parent session calls sessions_yield to wait on a subagent spawned with mode: "run" and the default delivery.mode: "announce", two outputs are produced for the same child completion:
The parent's yield-resume reply (a normal assistant turn, generated after the completion event) — committed to the transcript but never delivered to the external channel.
The subagent auto-announce direct-text delivery (delivery-mirror, …:text-direct), which delivers the child's raw result text — this one reaches the user.
Net effect: the user receives the subagent's raw completion summary (e.g. "Sync completed successfully. Here's the summary: …") instead of the parent agent's intended, natural-voice reply. The intended reply is silently dropped from the channel even though it exists in the session transcript.
This is not the same as the duplicate-message failure mode in #78369 (there the user sees both); here the user sees only the announce and the parent's reply is lost. It is also not#81490 (the yielded session was not orphaned — it resumed in place; see evidence below).
Steps to reproduce
From a parent session bound to an external channel (Feishu direct chat in this case), spawn a subagent with mode: "run" and the default delivery mode (announce / agentTurn).
Immediately call sessions_yield to wait for the child's completion event.
Let the child complete with a non-empty text result.
On resume, the parent generates a reply that depends on the child's output.
Observe the external channel: the user receives the child's raw result text (the auto-announce direct-text mirror), not the parent's resume reply.
Repro probability: intermittent. In the observed case the two transcript commits were ~672 ms apart, suggesting a timing-sensitive race between the announce direct-text delivery and the yield-resume reply delivery.
Expected behavior
Exactly one user-visible message per completion, and it should be the parent's intended resume reply. The auto-announce direct-text path should not deliver the child's raw summary when the parent has produced (or is about to produce) its own reply for the same completion.
Actual behavior
The parent's resume reply is written to the transcript but not delivered to the channel; the auto-announce direct-text mirror (the child's raw result) is delivered instead.
Evidence (verified from the parent session transcript)
customType: openclaw.sessions_yield (the yield marker)
—
57
assistant
deepseek-v4-pro
"飞书也同步好了 ✅ …周末先放一放 🦞" — the parent's resume reply, parentId = the yield marker (56)
❌ no
58
assistant
delivery-mirror
"Sync completed successfully. Here's the summary: …" — idempotencyKey: announce:v1:agent:main:subagent:09d6d986-…:9e662f80-…:text-direct
✅ yes
The user then confirmed in-channel (subsequent turns in the same transcript) that the last message they saw was "Sync completed successfully. Here's the summary: …" (line 58), and that the agent's reply (line 57) never arrived.
Line 58 has model: delivery-mirror, zero token usage, and the …:text-direct idempotency key — i.e. it is the auto-announce direct-text delivery of the child's raw result, not a parent LLM turn.
The two were committed ~672 ms apart (line 57 envelope …:37.244Z, line 58 envelope …:37.916Z).
Note: line 57 shows a ~13 s gap between its inner model timestamp and its transcript-commit timestamp. This is not anomalous — other replies the user did receive (later turns in the same session) show the same ~12 s gap. It is an artifact of the record format (inner = model completion, envelope = post-delivery commit), so it is not evidence of a stall. Mentioned here only to preempt that lead.
Likely mechanism (hypothesis — not fully pinned)
For the same child completion there appear to be two uncoordinated delivery producers:
The yield-resume path, which resumes the parent and produces a user-facing reply (line 57).
The subagent auto-announce path, which (for the announce default) ends up taking the direct-text branch and delivers the child's raw result (line 58) via sendMessage with a …:text-direct idempotency key + mirror.
The parent's resume reply is committed to the transcript but does not reach the channel, while the announce direct-text delivery does. The two are not deduplicated against each other for outbound delivery.
Relevant source paths (recovered from the //#region src/... markers in the published dist):
src/agents/subagent-announce-dispatch.ts — runSubagentAnnounceDispatch (the steer-vs-direct decision; when expectsCompletionMessage is true it runs direct() first).
src/agents/subagent-announce-delivery.ts — deliverTextCompletionDirect (produces the …:text-direct mirror using resolveTextCompletionDirectFallback, i.e. the child's result), maybeSteerSubagentAnnounce, runAnnounceAgentCall (note forceSyntheticClient for announce-mediated runs), sendSubagentAnnounceDirectly.
src/agents/announce-idempotency.ts — buildAnnounceIdempotencyKey / buildAnnounceIdFromChildRun (the announce:v1:<childSessionKey>:<childRunId> key seen on line 58).
The open question for maintainers: when a yielded parent resumes and produces its own reply for a completion, why is that reply's outbound delivery suppressed while the announce direct-text path still delivers the child's raw result? A structural dedup between the yield-resume reply and the announce delivery (cf. the fingerprint/short-circuit proposal in #78369) would prevent both the duplicate case (#78369) and this lost-reply case.
Subagent/delivery config was all defaults (no subagents/announce/delivery overrides in openclaw.json).
The spawn tool result itself carries guidance to not poll and to "wait for runtime completion events to arrive as user messages"; sessions_yield is an event-wait (not polling), so this appears to be a supported usage pattern rather than misuse.
Bug type
Runtime / message delivery (data loss — user-visible message lost)
Beta release blocker
No
Summary
When a parent session calls
sessions_yieldto wait on a subagent spawned withmode: "run"and the defaultdelivery.mode: "announce", two outputs are produced for the same child completion:delivery-mirror,…:text-direct), which delivers the child's raw result text — this one reaches the user.Net effect: the user receives the subagent's raw completion summary (e.g. "Sync completed successfully. Here's the summary: …") instead of the parent agent's intended, natural-voice reply. The intended reply is silently dropped from the channel even though it exists in the session transcript.
This is not the same as the duplicate-message failure mode in #78369 (there the user sees both); here the user sees only the announce and the parent's reply is lost. It is also not #81490 (the yielded session was not orphaned — it resumed in place; see evidence below).
Steps to reproduce
mode: "run"and the default delivery mode (announce/agentTurn).sessions_yieldto wait for the child's completion event.Repro probability: intermittent. In the observed case the two transcript commits were ~672 ms apart, suggesting a timing-sensitive race between the announce direct-text delivery and the yield-resume reply delivery.
Expected behavior
Exactly one user-visible message per completion, and it should be the parent's intended resume reply. The auto-announce direct-text path should not deliver the child's raw summary when the parent has produced (or is about to produce) its own reply for the same completion.
Actual behavior
The parent's resume reply is written to the transcript but not delivered to the channel; the auto-announce direct-text mirror (the child's raw result) is delivered instead.
Evidence (verified from the parent session transcript)
Session:
agent:main:feishu:direct:ou_24dea12546e7358d53bac7a00d32e8e1Child:
agent:main:subagent:09d6d986-a4fe-4fea-84f3-3471ef0dbec6(taskName: diary-feishu-sync,runId: 9e662f80-99d6-40fc-b1dc-63f82b115c13)Spawn:
mode: "run", defaultdelivery.mode: announce, channel = Feishu direct (websocket).Transcript order (parent session
.jsonl):sessions_yield→{"status":"yielded","message":"等待飞书同步完成"}customType: openclaw.sessions_yield(the yield marker)parentId= the yield marker (56)idempotencyKey: announce:v1:agent:main:subagent:09d6d986-…:9e662f80-…:text-directThe user then confirmed in-channel (subsequent turns in the same transcript) that the last message they saw was "Sync completed successfully. Here's the summary: …" (line 58), and that the agent's reply (line 57) never arrived.
Key points from the raw records:
provider: deepseek,model: deepseek-v4-pro, real token usage,stopReason: stop) whoseparentIdis theopenclaw.sessions_yieldmarker — i.e. the yielded session resumed in place and produced this reply (so this is not the orphaned-session case of [Bug]: subagent completion spawns a fresh run on the parent's route instead of resuming the yielded session (supersedes #80310) #81490).model: delivery-mirror, zero token usage, and the…:text-directidempotency key — i.e. it is the auto-announce direct-text delivery of the child's raw result, not a parent LLM turn.…:37.244Z, line 58 envelope…:37.916Z).Likely mechanism (hypothesis — not fully pinned)
For the same child completion there appear to be two uncoordinated delivery producers:
announcedefault) ends up taking the direct-text branch and delivers the child's raw result (line 58) viasendMessagewith a…:text-directidempotency key +mirror.The parent's resume reply is committed to the transcript but does not reach the channel, while the announce direct-text delivery does. The two are not deduplicated against each other for outbound delivery.
Relevant source paths (recovered from the
//#region src/...markers in the publisheddist):src/agents/subagent-announce-dispatch.ts—runSubagentAnnounceDispatch(the steer-vs-direct decision; whenexpectsCompletionMessageis true it runsdirect()first).src/agents/subagent-announce-delivery.ts—deliverTextCompletionDirect(produces the…:text-directmirror usingresolveTextCompletionDirectFallback, i.e. the child's result),maybeSteerSubagentAnnounce,runAnnounceAgentCall(noteforceSyntheticClientfor announce-mediated runs),sendSubagentAnnounceDirectly.src/agents/announce-idempotency.ts—buildAnnounceIdempotencyKey/buildAnnounceIdFromChildRun(theannounce:v1:<childSessionKey>:<childRunId>key seen on line 58).src/auto-reply/reply/completion-delivery-policy.ts—completionRequiresMessageToolDelivery.The open question for maintainers: when a yielded parent resumes and produces its own reply for a completion, why is that reply's outbound delivery suppressed while the announce direct-text path still delivers the child's raw result? A structural dedup between the yield-resume reply and the announce delivery (cf. the fingerprint/short-circuit proposal in #78369) would prevent both the duplicate case (#78369) and this lost-reply case.
Related issues
delivery-mirrorduplicates assistant text (related delivery-mirror surface, different channel/cause).OpenClaw version
2026.6.1 (2e08f0f)
Operating system
macOS 26 (Darwin 25.5.0, arm64), Node v22.22.3. Channel: Feishu,
connectionMode: websocket.Additional information
subagents/announce/deliveryoverrides inopenclaw.json).sessions_yieldis an event-wait (not polling), so this appears to be a supported usage pattern rather than misuse.