Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
This is a follow-up to #87989 because GitHub will not let me reopen that issue after it was closed as completed. The original issue is not fixed in the latest beta.
After updating the affected Discord bot instance to 2026.5.28-beta.1 (84fd629), a single successful image_generate completion still sent the same generated Discord attachment 12 times.
Steps to reproduce
- Run a Discord channel agent on OpenClaw
2026.5.28-beta.1.
- Ask the agent to generate an image for the current channel.
- Wait for the background
image_generate:<taskId>:ok completion wake to route back to the requester session.
- Observe repeated
message.send calls with the same generated media path and different captions.
Expected behavior
A successful image_generate completion should deliver each generated attachment exactly once to the original chat, then stop.
Historical message-tool calls, explicit route fields, or requester-session context should not be able to cause repeated delivery of the same completion artifact.
Actual behavior
One image_generate completion delivered the same generated JPEG 12 times to the same Discord channel. The run eventually stopped, so this is bounded duplicate delivery, not an infinite Discord retry loop.
Environment
OpenClaw 2026.5.28-beta.1 (84fd629)
Service: systemd user (enabled)
File logs: /tmp/openclaw/openclaw-2026-05-29.log
Install method: npm global
Gateway: systemd user service
Model for requester/completion session: ollama/glm-5.1:cloud
Image provider/model from completion event: google/gemini-3.1-flash-image-preview
Reproduction evidence
Run: image_generate:5e66cfd7-9cff-4edd-86b5-85d5aec8e2fe:ok
Session key: agent:main:discord:channel:1507887702379335791
Media path: ~/.openclaw/media/tool-image-generation/image-1---34799bf3-36d1-462a-8e08-3490a7e58a28.jpg
Discord attachment filename: image-1---34799bf3-36d1-462a-8e08-3490a7e58a28.jpg
Image size: 1197745 bytes, 1024x1024 JPEG
Requester session JSONL:
message tool calls with that exact path: 12
successful message tool results: 12
delivery mirror idempotency keys: 12
Public Discord channel read:
visible posts with that exact attachment filename: 12
Receipt summary:
01. 2026-05-29T10:40:36.989Z -> 1509869060245164055; routeKeys=channel; call=call_ulah6cgi
02. 2026-05-29T10:40:45.759Z -> 1509869098979561564; routeKeys=channel; call=call_ndkp55eb
03. 2026-05-29T10:40:53.712Z -> 1509869133053952060; routeKeys=channel; call=call_rl6o2078
04. 2026-05-29T10:41:00.283Z -> 1509869161067577508; routeKeys=target; call=call_fgt36hia
05. 2026-05-29T10:41:07.861Z -> 1509869192646492310; routeKeys=channel; call=call_94wri2z2
06. 2026-05-29T10:41:13.570Z -> 1509869216399097877; routeKeys=channel; call=call_jbqbwmkg
07. 2026-05-29T10:41:20.524Z -> 1509869244219785326; routeKeys=channel; call=call_uqiogzbp
08. 2026-05-29T10:41:25.994Z -> 1509869268496289884; routeKeys=channel; call=call_ljup701l
09. 2026-05-29T10:41:35.900Z -> 1509869305628463204; routeKeys=channel; call=call_1teg1h4j
10. 2026-05-29T10:41:41.837Z -> 1509869335076802711; routeKeys=channel; call=call_jz16jss4
11. 2026-05-29T10:41:59.827Z -> 1509869410498646137; routeKeys=channel; call=call_oqmy5de6
12. 2026-05-29T10:42:13.597Z -> 1509869466987794512; routeKeys=channel+target; call=call_qepozibv
The requester session then stopped only after the model noticed it had looped:
The image has already been sent to the channel (multiple times, unfortunately — I was looping again). I will NOT send it again.
MEDIA:~/.openclaw/media/tool-image-generation/image-1---34799bf3-36d1-462a-8e08-3490a7e58a28.jpg
Why #83022 / the terminal guard is not sufficient
The closure on #87989 pointed to the message-tool terminal guard as already fixing this. That guard exists in current main and in the tested beta, but it does not cover this reproduced path.
Current main's message-tool-terminal.ts returns false for explicit route sends:
if (!isMessageToolSendActionName(args.action) || hasExplicitMessageRoute(args)) {
return false;
}
Every duplicate send in this reproduction used an explicit route key: channel, target, or both. So the terminal guard is present, but it deliberately does not terminate the duplicate sends seen here.
Completion delivery path still delegates to the requester LLM first
Current main's generated-media completion path still makes direct generated-media delivery fallback-only while the requester session is active:
const tryGeneratedMediaDirectDelivery = async (announceResponse?: unknown) => {
if (requesterActivity.isActive && !activeRequesterWakeFailed) {
return undefined;
}
...
};
const completionSourceReplyDeliveryMode = requiresMessageToolDelivery
? "message_tool_only"
: undefined;
const wakeOutcome = await resolveActiveWakeWithRetries(
requesterActivity.sessionId,
params.triggerMessage,
wakeOptions,
params.signal,
);
That means successful generated-media delivery is still handed to an LLM wake first, with direct delivery only as fallback. The current terminal guard only handles a subset of message-tool sends after delivery; it does not enforce artifact-level idempotency.
Impact and severity
Severity: Medium.
This does not crash the gateway, but it spams channels, repeats uploads, wastes tool/provider work, and makes image completion delivery unreliable.
Suggested fix
Either:
- Prefer deterministic direct delivery for generated-media completions when expected media URLs are present, before waking the requester LLM; or
- Add a hard idempotency guard keyed by
image_generate:<taskId>:ok + target + mediaUrls, and stop/ignore any later send attempts for the same artifact.
Draft PR #87991 implements the direct-first approach.
Bug type
Behavior bug (incorrect output/state without crash)
Beta release blocker
No
Summary
This is a follow-up to #87989 because GitHub will not let me reopen that issue after it was closed as completed. The original issue is not fixed in the latest beta.
After updating the affected Discord bot instance to
2026.5.28-beta.1 (84fd629), a single successfulimage_generatecompletion still sent the same generated Discord attachment 12 times.Steps to reproduce
2026.5.28-beta.1.image_generate:<taskId>:okcompletion wake to route back to the requester session.message.sendcalls with the same generated media path and different captions.Expected behavior
A successful
image_generatecompletion should deliver each generated attachment exactly once to the original chat, then stop.Historical message-tool calls, explicit route fields, or requester-session context should not be able to cause repeated delivery of the same completion artifact.
Actual behavior
One
image_generatecompletion delivered the same generated JPEG 12 times to the same Discord channel. The run eventually stopped, so this is bounded duplicate delivery, not an infinite Discord retry loop.Environment
Reproduction evidence
Receipt summary:
The requester session then stopped only after the model noticed it had looped:
Why #83022 / the terminal guard is not sufficient
The closure on #87989 pointed to the message-tool terminal guard as already fixing this. That guard exists in current main and in the tested beta, but it does not cover this reproduced path.
Current main's
message-tool-terminal.tsreturns false for explicit route sends:Every duplicate send in this reproduction used an explicit route key:
channel,target, or both. So the terminal guard is present, but it deliberately does not terminate the duplicate sends seen here.Completion delivery path still delegates to the requester LLM first
Current main's generated-media completion path still makes direct generated-media delivery fallback-only while the requester session is active:
That means successful generated-media delivery is still handed to an LLM wake first, with direct delivery only as fallback. The current terminal guard only handles a subset of message-tool sends after delivery; it does not enforce artifact-level idempotency.
Impact and severity
Severity: Medium.
This does not crash the gateway, but it spams channels, repeats uploads, wastes tool/provider work, and makes image completion delivery unreliable.
Suggested fix
Either:
image_generate:<taskId>:ok + target + mediaUrls, and stop/ignore any later send attempts for the same artifact.Draft PR #87991 implements the direct-first approach.