Bug Summary
Discord's "typing" indicator continues showing after an agent has completed its reply and posted the final message. In some cases, the typing indicator persists indefinitely — well beyond the 2-minute TTL failsafe — giving users the false impression the agent is still working when no tokens are being consumed.
Steps to Reproduce
- Send a message to any agent via Discord
- Agent processes the request, streams/posts its reply
- After the final message is posted, observe that the "typing..." indicator continues showing in the Discord channel
- No tokens are being burned (confirmed via
openclaw status — session shows stale "last active" timestamp)
Expected Behavior
The typing indicator should stop immediately (or within a few seconds) after the agent posts its final reply.
Actual Behavior
- Short case: Typing persists for up to 2 minutes after completion (the hard-coded TTL in
typing-lifecycle.ts)
- Severe case: Typing persists indefinitely, surviving past the 2-minute TTL. Observed with agent "friday" — typing showed continuously with zero token burn, session file untouched for 15+ minutes. Required
openclaw gateway restart to clear.
Root Cause Analysis
Traced through the bundled source (reply-Cx57rl6c.js). The typing system has a three-layer architecture:
1. Race condition in createTypingController (source: src/auto-reply/reply/typing.ts)
Typing cleanup requires two flags to both be true:
const maybeStopOnIdle = () => {
if (!active) return;
if (runComplete && dispatchIdle) cleanup(); // BOTH must be true
};
markRunComplete() is called in runPreparedReply's finally block
markDispatchIdle() is called in the Discord message handler's finally block
These are separate code paths that don't fire atomically. When the agent run ends but the dispatcher hasn't flushed all outbound messages, the setInterval keepalive loop (every 6 seconds) continues calling channel.triggerTyping().
2. No explicit Discord "stop typing" API
Discord's typing indicator expires after ~10 seconds naturally, but the keepalive loop refreshes it every 6 seconds (typingIntervalSeconds default). The fireStop() method in createTypingCallbacks checks for a stop callback, but the Discord channel adapter doesn't provide one (unlike e.g. Telegram), so the only way to stop is clearing the setInterval.
3. TTL failsafe may not always fire
The 2-minute TTL (typingTtlMs = 2 * 6e4) sets a setTimeout for unconditional cleanup. However, in the severe case observed, typing persisted well beyond 2 minutes. Hypothesis: Discord WebSocket disconnects/resumes (observed every ~15-20 minutes in gateway logs, codes 1005/1006) may re-trigger or re-initialize typing state for sessions that had pending typing when the connection dropped.
Environment
- OpenClaw: 2026.2.24 (stable)
- Node: v24.13.0 (nvm)
- Platform: macOS 26.3 (arm64)
- Channel: Discord
- Gateway: LaunchAgent, local loopback
Suggested Fixes
- Decouple cleanup from dual-flag gate: Stop the typing keepalive as soon as the final message is posted, rather than waiting for both
runComplete and dispatchIdle
- Clear typing state on Discord resume: When the Discord WebSocket reconnects after a 1005/1006 close, ensure no stale typing loops survive from the previous connection
- Make TTL configurable: The 2-minute TTL is hard-coded; exposing it in config would let users reduce it
- Add explicit cleanup on session idle: If no model call has been made for N seconds but the typing loop is still running, force-stop it
Workaround
openclaw gateway restart # clears all stuck typing state
Or configure typingMode: "message" (or "never") in openclaw.json to reduce/eliminate the issue:
{
agents: {
defaults: {
typingMode: "message" // only shows typing when text starts streaming
}
}
}
Bug Summary
Discord's "typing" indicator continues showing after an agent has completed its reply and posted the final message. In some cases, the typing indicator persists indefinitely — well beyond the 2-minute TTL failsafe — giving users the false impression the agent is still working when no tokens are being consumed.
Steps to Reproduce
openclaw status— session shows stale "last active" timestamp)Expected Behavior
The typing indicator should stop immediately (or within a few seconds) after the agent posts its final reply.
Actual Behavior
typing-lifecycle.ts)openclaw gateway restartto clear.Root Cause Analysis
Traced through the bundled source (
reply-Cx57rl6c.js). The typing system has a three-layer architecture:1. Race condition in
createTypingController(source:src/auto-reply/reply/typing.ts)Typing cleanup requires two flags to both be
true:markRunComplete()is called inrunPreparedReply'sfinallyblockmarkDispatchIdle()is called in the Discord message handler'sfinallyblockThese are separate code paths that don't fire atomically. When the agent run ends but the dispatcher hasn't flushed all outbound messages, the
setIntervalkeepalive loop (every 6 seconds) continues callingchannel.triggerTyping().2. No explicit Discord "stop typing" API
Discord's typing indicator expires after ~10 seconds naturally, but the keepalive loop refreshes it every 6 seconds (
typingIntervalSecondsdefault). ThefireStop()method increateTypingCallbackschecks for astopcallback, but the Discord channel adapter doesn't provide one (unlike e.g. Telegram), so the only way to stop is clearing thesetInterval.3. TTL failsafe may not always fire
The 2-minute TTL (
typingTtlMs = 2 * 6e4) sets asetTimeoutfor unconditional cleanup. However, in the severe case observed, typing persisted well beyond 2 minutes. Hypothesis: Discord WebSocket disconnects/resumes (observed every ~15-20 minutes in gateway logs, codes 1005/1006) may re-trigger or re-initialize typing state for sessions that had pending typing when the connection dropped.Environment
Suggested Fixes
runCompleteanddispatchIdleWorkaround
openclaw gateway restart # clears all stuck typing stateOr configure
typingMode: "message"(or"never") inopenclaw.jsonto reduce/eliminate the issue: