Summary
#89995 was closed as "already implemented" because PR #89812 (commit 79896a24) wraps the post-delivery transcript mirror in try/catch inside deliverOutboundPayloadsCore. That fix is real, but it does not cover the subagent-announce delivery path. The same EmbeddedPromptLockFileChangedError: session file changed while embedded prompt lock was released still triggers 3× duplicate Telegram sends on the 2026.6.1 stable release (not just the 2026.6.1-beta.3 reported in #89995).
Environment
|
|
| OpenClaw |
2026.6.1 release (npm [email protected]) |
| Channel |
Telegram (direct DM, accountId=writer, chatId=51406718) |
| OS |
Linux 6.17.0-1024-oem (Ubuntu) |
| Node |
v22.22.2 |
| Spawn pattern |
sessions_spawn(taskName=..., mode="run") + sessions_yield |
| Reproducible |
100% on this setup (1/1 observed so far) |
Steps to reproduce
- From a Telegram DM parent session, call
sessions_spawn(taskName="…", mode="run") with a long-running subagent task
- Parent calls
sessions_yield to wait for the auto-announce
- The parent session is concurrently being written by the parent's own turn maintenance (the deferred turn-maintenance lane queues around the same time as the subagent's completion event)
- Subagent completes; gateway tries to inject the final text into the parent session
- Inject throws
EmbeddedPromptLockFileChangedError because the parent jsonl changed while the embedded prompt lock was released
- Gateway's announce retry mechanism (in
subagent-registry-dq-LC-2-.js:1037, maxAnnounceRetryCount: 3) fires 3 times with exponential backoff
- Each retry independently triggers a Telegram outbound send → user receives 3 identical messages
Why PR #89812 is not sufficient
PR #89812 only modified:
src/infra/outbound/deliver.ts (+24, -8)
src/infra/outbound/deliver.test.ts (+62, -1)
The fix wraps appendAssistantMessageToSessionTranscript(...) in try/catch so a mirror failure no longer fails the overall deliverOutboundPayloadsCore call. That stops the retry-on-mirror-failure path for the general outbound layer (matrix/telegram/etc. when an agent's reply is being sent to the user).
The subagent-announce path is a separate delivery module:
src/agents/subagent-announce-delivery.ts (runtime subagent-announce-delivery-DhVEJzi6.js)
src/agents/subagent-registry-lifecycle.ts (runtime subagent-registry-dq-LC-2-.js, retry orchestration)
That path still classifies the lock-change error as retriable and has no idempotency check between the in-band delivery attempt and the external outbound send. So the bug reproduced on the 2026.6.1 stable release, not just on beta.
Evidence (local journalctl excerpt)
07:24:00.400 [MiniMax-M3] (subagent) ✅ 任務完成 …(child session output)…
07:24:22.754 [agent/embedded] [context-engine] deferred turn maintenance queued
lane=context-engine-turn-maintenance:agent:writer:telegram:direct:51406718
07:24:23.899 [telegram] outbound send ok accountId=writer chatId=51406718 messageId=2624
07:24:23.978 [warn] Subagent completion direct announce failed for run b434f673-…:
text completion direct delivery failed:
session file changed while embedded prompt lock was released:
/home/zackchiu/.openclaw/agents/writer/sessions/6512e51b-….jsonl
07:24:25.716 [telegram] outbound send ok messageId=2625
07:24:25.731 [warn] Subagent completion direct announce failed … (retry 2)
07:24:28.500 [telegram] outbound send ok messageId=2626
07:24:28.517 [warn] Subagent completion direct announce failed … (retry 3)
07:24:28.700 [warn] Subagent announce give up (retry-limit) retries=3 endedAgo=29s
User report from the parent: "你摘要網頁又連發三次一樣的訊息了" (2026-06-09 07:39 local).
Related issues / PRs
Suggested fix (mirroring #89812's pattern)
Either:
-
Add EmbeddedPromptLockFileChangedError / "session file changed while embedded prompt lock was released" to PERMANENT_ANNOUNCE_DELIVERY_ERROR_PATTERNS in the announce-delivery module, so the lock race stops triggering retries at all.
-
Or add an idempotency token check before each announce retry, mirroring what deliverOutboundPayloadsCore now does: if the outbound send already succeeded in a prior attempt, the retry must not re-send. A lastDeliveredOutboundAt / outboundDeliveryId field on the announce entry, set when [telegram] outbound send ok is logged, would do it.
Option 1 is the smaller, safer change and matches the suggestion in the original #89995 report.
Workaround (in our setup, until this lands)
agents.defaults.subagents.announceTimeoutMs: 0 and agent-side deduplication on (childRunId, 60s) for incoming completion events. Not great — the proper fix is upstream.
Summary
#89995was closed as "already implemented" because PR #89812 (commit79896a24) wraps the post-delivery transcript mirror intry/catchinsidedeliverOutboundPayloadsCore. That fix is real, but it does not cover the subagent-announce delivery path. The sameEmbeddedPromptLockFileChangedError: session file changed while embedded prompt lock was releasedstill triggers 3× duplicate Telegram sends on the 2026.6.1 stable release (not just the 2026.6.1-beta.3 reported in #89995).Environment
[email protected])sessions_spawn(taskName=..., mode="run")+sessions_yieldSteps to reproduce
sessions_spawn(taskName="…", mode="run")with a long-running subagent tasksessions_yieldto wait for the auto-announceEmbeddedPromptLockFileChangedErrorbecause the parent jsonl changed while the embedded prompt lock was releasedsubagent-registry-dq-LC-2-.js:1037,maxAnnounceRetryCount: 3) fires 3 times with exponential backoffWhy PR #89812 is not sufficient
PR #89812 only modified:
src/infra/outbound/deliver.ts(+24, -8)src/infra/outbound/deliver.test.ts(+62, -1)The fix wraps
appendAssistantMessageToSessionTranscript(...)intry/catchso a mirror failure no longer fails the overalldeliverOutboundPayloadsCorecall. That stops the retry-on-mirror-failure path for the general outbound layer (matrix/telegram/etc. when an agent's reply is being sent to the user).The subagent-announce path is a separate delivery module:
src/agents/subagent-announce-delivery.ts(runtimesubagent-announce-delivery-DhVEJzi6.js)src/agents/subagent-registry-lifecycle.ts(runtimesubagent-registry-dq-LC-2-.js, retry orchestration)That path still classifies the lock-change error as retriable and has no idempotency check between the in-band delivery attempt and the external outbound send. So the bug reproduced on the 2026.6.1 stable release, not just on beta.
Evidence (local journalctl excerpt)
User report from the parent: "你摘要網頁又連發三次一樣的訊息了" (2026-06-09 07:39 local).
Related issues / PRs
fix(subagents): prevent message_tool_only from swallowing subagent completion messagefix(announce): durable in-process queue fallback for direct-pending handoffsfix(agents): harden subagent announce retry and error handling(most likely the right place to land a permanent/permanent classification of the lock error)fix(agents): skip text-direct fallback for sessions_yield completionsfix(agents): preserve subagent wake delivery routeSuggested fix (mirroring #89812's pattern)
Either:
Add
EmbeddedPromptLockFileChangedError/ "session file changed while embedded prompt lock was released" toPERMANENT_ANNOUNCE_DELIVERY_ERROR_PATTERNSin the announce-delivery module, so the lock race stops triggering retries at all.Or add an idempotency token check before each announce retry, mirroring what
deliverOutboundPayloadsCorenow does: if the outbound send already succeeded in a prior attempt, the retry must not re-send. AlastDeliveredOutboundAt/outboundDeliveryIdfield on the announce entry, set when[telegram] outbound send okis logged, would do it.Option 1 is the smaller, safer change and matches the suggestion in the original #89995 report.
Workaround (in our setup, until this lands)
agents.defaults.subagents.announceTimeoutMs: 0and agent-side deduplication on(childRunId, 60s)for incoming completion events. Not great — the proper fix is upstream.