-
-
Notifications
You must be signed in to change notification settings - Fork 52.6k
Description
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 BOTHmarkRunComplete()ANDmarkDispatchIdle()to triggercleanup()viamaybeStopOnIdle():const maybeStopOnIdle = () => { if (!active) return; if (runComplete && dispatchIdle) cleanup(); };
-
processDiscordMessage'sfinallyblock (line ~49946) only callsmarkDispatchIdle():} 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, somaybeStopOnIdle()returns early (runCompleteis stillfalse), 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