Bug Description
When an agent run ends with a silent reply (NO_REPLY / SILENT_REPLY_TOKEN), the Discord typing indicator ("X is typing...") persists indefinitely. The triggerTyping keepalive loop never stops.
Expected Behavior
When a run ends — regardless of whether the reply is silent, NO_REPLY, or normal — the typing keepalive loop should be stopped so the typing indicator clears after Discord's natural ~10 second timeout.
Actual Behavior
The typing keepalive loop continues sending triggerTyping() every 3 seconds forever, causing a permanent "is typing..." indicator in the Discord channel.
Root Cause (from source analysis)
File: src/discord/monitor/message-handler.process.ts → compiled as pi-embedded-BDhvoWGL.js
The issue is in the interaction between createTypingController and the Discord message handler's finally block:
-
createTypingController (line ~42567) requires BOTH markRunComplete() AND markDispatchIdle() to trigger cleanup() via maybeStopOnIdle():
const maybeStopOnIdle = () => {
if (!active) return;
if (runComplete && dispatchIdle) cleanup();
};
-
processDiscordMessage's finally block (line ~49946) only calls markDispatchIdle():
} finally {
await draftStream?.stop();
if (!finalizedViaPreviewMessage) await draftStream?.clear();
markDispatchIdle(); // ← only this is called
...
}
-
When a run ends with NO_REPLY/silent token, markRunComplete() is never called on the typing controller, so maybeStopOnIdle() returns early (runComplete is still false), and the keepalive loop runs forever.
Suggested Fix
In the finally block of processDiscordMessage, explicitly call typing.cleanup() (or ensure markRunComplete() is always called when the run ends) so the keepalive loop stops regardless of the reply outcome:
} finally {
await draftStream?.stop();
if (!finalizedViaPreviewMessage) await draftStream?.clear();
markDispatchIdle();
typingCallbacks.onCleanup?.(); // ← add explicit cleanup
...
}
Alternatively, the createTypingController's markDispatchIdle could unconditionally stop the keepalive loop, since if dispatch is idle, there's no reason to keep typing.
Workaround
Setting agents.defaults.typingMode: "never" disables the typing indicator entirely, preventing the bug but losing visual feedback.
Environment
- OpenClaw version:
2026.2.24
- Channel: Discord (Bot API)
- macOS, gateway via launchd LaunchAgent
Bug Description
When an agent run ends with a silent reply (NO_REPLY /
SILENT_REPLY_TOKEN), the Discord typing indicator ("X is typing...") persists indefinitely. ThetriggerTypingkeepalive loop never stops.Expected Behavior
When a run ends — regardless of whether the reply is silent, NO_REPLY, or normal — the typing keepalive loop should be stopped so the typing indicator clears after Discord's natural ~10 second timeout.
Actual Behavior
The typing keepalive loop continues sending
triggerTyping()every 3 seconds forever, causing a permanent "is typing..." indicator in the Discord channel.Root Cause (from source analysis)
File:
src/discord/monitor/message-handler.process.ts→ compiled aspi-embedded-BDhvoWGL.jsThe issue is in the interaction between
createTypingControllerand the Discord message handler'sfinallyblock:createTypingController(line ~42567) requires BOTHmarkRunComplete()ANDmarkDispatchIdle()to triggercleanup()viamaybeStopOnIdle():processDiscordMessage'sfinallyblock (line ~49946) only callsmarkDispatchIdle():When a run ends with NO_REPLY/silent token,
markRunComplete()is never called on the typing controller, somaybeStopOnIdle()returns early (runCompleteis stillfalse), and the keepalive loop runs forever.Suggested Fix
In the
finallyblock ofprocessDiscordMessage, explicitly calltyping.cleanup()(or ensuremarkRunComplete()is always called when the run ends) so the keepalive loop stops regardless of the reply outcome:Alternatively, the
createTypingController'smarkDispatchIdlecould unconditionally stop the keepalive loop, since if dispatch is idle, there's no reason to keep typing.Workaround
Setting
agents.defaults.typingMode: "never"disables the typing indicator entirely, preventing the bug but losing visual feedback.Environment
2026.2.24