Skip to content

fix(discord): prevent stuck typing indicator#26295

Merged
ngutman merged 2 commits intomainfrom
fix/discord-typing-stuck
Feb 25, 2026
Merged

fix(discord): prevent stuck typing indicator#26295
ngutman merged 2 commits intomainfrom
fix/discord-typing-stuck

Conversation

@ngutman
Copy link
Contributor

@ngutman ngutman commented Feb 25, 2026

Summary

  • prevent channel typing keepalive from restarting after idle or cleanup by sealing typing callbacks once stopped
  • ensure Discord dispatch always marks typing idle even when draft stream shutdown throws
  • add regression coverage for post-cleanup typing restart behavior

Root Cause

Typing keepalive introduced in createTypingCallbacks could be re-armed by a late onReplyStart after idle cleanup, and Discord could skip markDispatchIdle() if draft cleanup threw. Either path could leave the typing indicator active indefinitely.

Testing

  • pnpm vitest run src/channels/typing.test.ts
  • pnpm vitest run src/discord/monitor/message-handler.process.test.ts

Greptile Summary

Fixes Discord typing indicator getting stuck by preventing keepalive restart after cleanup and ensuring cleanup errors don't skip idle marking.

  • Added closed flag in src/channels/typing.ts:20 to seal typing callbacks once stopped, preventing late onReplyStart from restarting keepalive after onIdle/onCleanup
  • Wrapped Discord draft cleanup in try-finally at src/discord/monitor/message-handler.process.ts:726-737 to ensure markDispatchIdle() always executes even if draft stream throws
  • Added regression test coverage for post-cleanup typing restart behavior

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The fix is surgical and well-tested. The closed flag pattern is a clean way to prevent state machine re-entry after cleanup. The nested try-finally in Discord ensures typing cleanup always completes. Regression test coverage validates the fix. No side effects or breaking changes detected.
  • No files require special attention

Last reviewed commit: 3f37a5e

@openclaw-barnacle openclaw-barnacle bot added channel: discord Channel integration: discord size: XS maintainer Maintainer-authored PR labels Feb 25, 2026
@ngutman ngutman merged commit 56b8c69 into main Feb 25, 2026
7 checks passed
@ngutman ngutman deleted the fix/discord-typing-stuck branch February 25, 2026 08:21
@ngutman
Copy link
Contributor Author

ngutman commented Feb 25, 2026

Landed via temp rebase onto main.

  • Gate: pnpm lint && pnpm build && pnpm test (ran; failed in unrelated extension suites and hit Node OOM during full test run)
  • Land commit: d1de9d5
  • Merge commit: 56b8c69

Thanks @ngutman!

akropp pushed a commit to akropp/openclaw that referenced this pull request Feb 25, 2026
Jackson3195 pushed a commit to Jackson3195/openclaw-with-a-personal-touch that referenced this pull request Feb 25, 2026
steipete pushed a commit that referenced this pull request Feb 26, 2026
…k indicator (#26881)

The followup runner (used for queued messages, inter-agent sends,
heartbeat followups, etc.) only called typing.markRunComplete() in
its finally block.  The typing controller requires BOTH markRunComplete
AND markDispatchIdle to trigger cleanup — but markDispatchIdle was
only wired through the buffered dispatcher path, which followup turns
bypass entirely.

This caused the typing indicator to persist indefinitely on channels
like Telegram when the agent replied with NO_REPLY or produced empty
payloads, because the keepalive loop was never stopped.

Adds markDispatchIdle() alongside markRunComplete() in the followup
runner's finally block, and four test cases covering NO_REPLY, empty
payloads, agent errors, and successful delivery.

Complements #26295 which addressed the channel-level callback layer.

Fixes #26595

Co-authored-by: Samantha <samantha@Samanthas-Mac-mini.local>
brianleach pushed a commit to brianleach/openclaw that referenced this pull request Feb 26, 2026
brianleach pushed a commit to brianleach/openclaw that referenced this pull request Feb 26, 2026
…k indicator (openclaw#26881)

The followup runner (used for queued messages, inter-agent sends,
heartbeat followups, etc.) only called typing.markRunComplete() in
its finally block.  The typing controller requires BOTH markRunComplete
AND markDispatchIdle to trigger cleanup — but markDispatchIdle was
only wired through the buffered dispatcher path, which followup turns
bypass entirely.

This caused the typing indicator to persist indefinitely on channels
like Telegram when the agent replied with NO_REPLY or produced empty
payloads, because the keepalive loop was never stopped.

Adds markDispatchIdle() alongside markRunComplete() in the followup
runner's finally block, and four test cases covering NO_REPLY, empty
payloads, agent errors, and successful delivery.

Complements openclaw#26295 which addressed the channel-level callback layer.

Fixes openclaw#26595

Co-authored-by: Samantha <samantha@Samanthas-Mac-mini.local>
execute008 pushed a commit to execute008/openclaw that referenced this pull request Feb 27, 2026
execute008 pushed a commit to execute008/openclaw that referenced this pull request Feb 27, 2026
…k indicator (openclaw#26881)

The followup runner (used for queued messages, inter-agent sends,
heartbeat followups, etc.) only called typing.markRunComplete() in
its finally block.  The typing controller requires BOTH markRunComplete
AND markDispatchIdle to trigger cleanup — but markDispatchIdle was
only wired through the buffered dispatcher path, which followup turns
bypass entirely.

This caused the typing indicator to persist indefinitely on channels
like Telegram when the agent replied with NO_REPLY or produced empty
payloads, because the keepalive loop was never stopped.

Adds markDispatchIdle() alongside markRunComplete() in the followup
runner's finally block, and four test cases covering NO_REPLY, empty
payloads, agent errors, and successful delivery.

Complements openclaw#26295 which addressed the channel-level callback layer.

Fixes openclaw#26595

Co-authored-by: Samantha <samantha@Samanthas-Mac-mini.local>
r4jiv007 pushed a commit to r4jiv007/openclaw that referenced this pull request Feb 28, 2026
r4jiv007 pushed a commit to r4jiv007/openclaw that referenced this pull request Feb 28, 2026
…k indicator (openclaw#26881)

The followup runner (used for queued messages, inter-agent sends,
heartbeat followups, etc.) only called typing.markRunComplete() in
its finally block.  The typing controller requires BOTH markRunComplete
AND markDispatchIdle to trigger cleanup — but markDispatchIdle was
only wired through the buffered dispatcher path, which followup turns
bypass entirely.

This caused the typing indicator to persist indefinitely on channels
like Telegram when the agent replied with NO_REPLY or produced empty
payloads, because the keepalive loop was never stopped.

Adds markDispatchIdle() alongside markRunComplete() in the followup
runner's finally block, and four test cases covering NO_REPLY, empty
payloads, agent errors, and successful delivery.

Complements openclaw#26295 which addressed the channel-level callback layer.

Fixes openclaw#26595

Co-authored-by: Samantha <samantha@Samanthas-Mac-mini.local>
vincentkoc pushed a commit to Sid-Qin/openclaw that referenced this pull request Feb 28, 2026
…k indicator (openclaw#26881)

The followup runner (used for queued messages, inter-agent sends,
heartbeat followups, etc.) only called typing.markRunComplete() in
its finally block.  The typing controller requires BOTH markRunComplete
AND markDispatchIdle to trigger cleanup — but markDispatchIdle was
only wired through the buffered dispatcher path, which followup turns
bypass entirely.

This caused the typing indicator to persist indefinitely on channels
like Telegram when the agent replied with NO_REPLY or produced empty
payloads, because the keepalive loop was never stopped.

Adds markDispatchIdle() alongside markRunComplete() in the followup
runner's finally block, and four test cases covering NO_REPLY, empty
payloads, agent errors, and successful delivery.

Complements openclaw#26295 which addressed the channel-level callback layer.

Fixes openclaw#26595

Co-authored-by: Samantha <samantha@Samanthas-Mac-mini.local>
vincentkoc pushed a commit to rylena/rylen-openclaw that referenced this pull request Feb 28, 2026
vincentkoc pushed a commit to rylena/rylen-openclaw that referenced this pull request Feb 28, 2026
…k indicator (openclaw#26881)

The followup runner (used for queued messages, inter-agent sends,
heartbeat followups, etc.) only called typing.markRunComplete() in
its finally block.  The typing controller requires BOTH markRunComplete
AND markDispatchIdle to trigger cleanup — but markDispatchIdle was
only wired through the buffered dispatcher path, which followup turns
bypass entirely.

This caused the typing indicator to persist indefinitely on channels
like Telegram when the agent replied with NO_REPLY or produced empty
payloads, because the keepalive loop was never stopped.

Adds markDispatchIdle() alongside markRunComplete() in the followup
runner's finally block, and four test cases covering NO_REPLY, empty
payloads, agent errors, and successful delivery.

Complements openclaw#26295 which addressed the channel-level callback layer.

Fixes openclaw#26595

Co-authored-by: Samantha <samantha@Samanthas-Mac-mini.local>
hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Mar 1, 2026
steipete pushed a commit to Sid-Qin/openclaw that referenced this pull request Mar 2, 2026
steipete pushed a commit to Sid-Qin/openclaw that referenced this pull request Mar 2, 2026
…k indicator (openclaw#26881)

The followup runner (used for queued messages, inter-agent sends,
heartbeat followups, etc.) only called typing.markRunComplete() in
its finally block.  The typing controller requires BOTH markRunComplete
AND markDispatchIdle to trigger cleanup — but markDispatchIdle was
only wired through the buffered dispatcher path, which followup turns
bypass entirely.

This caused the typing indicator to persist indefinitely on channels
like Telegram when the agent replied with NO_REPLY or produced empty
payloads, because the keepalive loop was never stopped.

Adds markDispatchIdle() alongside markRunComplete() in the followup
runner's finally block, and four test cases covering NO_REPLY, empty
payloads, agent errors, and successful delivery.

Complements openclaw#26295 which addressed the channel-level callback layer.

Fixes openclaw#26595

Co-authored-by: Samantha <samantha@Samanthas-Mac-mini.local>
hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Mar 3, 2026
dorgonman pushed a commit to kanohorizonia/openclaw that referenced this pull request Mar 3, 2026
…k indicator (openclaw#26881)

The followup runner (used for queued messages, inter-agent sends,
heartbeat followups, etc.) only called typing.markRunComplete() in
its finally block.  The typing controller requires BOTH markRunComplete
AND markDispatchIdle to trigger cleanup — but markDispatchIdle was
only wired through the buffered dispatcher path, which followup turns
bypass entirely.

This caused the typing indicator to persist indefinitely on channels
like Telegram when the agent replied with NO_REPLY or produced empty
payloads, because the keepalive loop was never stopped.

Adds markDispatchIdle() alongside markRunComplete() in the followup
runner's finally block, and four test cases covering NO_REPLY, empty
payloads, agent errors, and successful delivery.

Complements openclaw#26295 which addressed the channel-level callback layer.

Fixes openclaw#26595

Co-authored-by: Samantha <samantha@Samanthas-Mac-mini.local>
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
…k indicator (openclaw#26881)

The followup runner (used for queued messages, inter-agent sends,
heartbeat followups, etc.) only called typing.markRunComplete() in
its finally block.  The typing controller requires BOTH markRunComplete
AND markDispatchIdle to trigger cleanup — but markDispatchIdle was
only wired through the buffered dispatcher path, which followup turns
bypass entirely.

This caused the typing indicator to persist indefinitely on channels
like Telegram when the agent replied with NO_REPLY or produced empty
payloads, because the keepalive loop was never stopped.

Adds markDispatchIdle() alongside markRunComplete() in the followup
runner's finally block, and four test cases covering NO_REPLY, empty
payloads, agent errors, and successful delivery.

Complements openclaw#26295 which addressed the channel-level callback layer.

Fixes openclaw#26595

Co-authored-by: Samantha <samantha@Samanthas-Mac-mini.local>
thebenjaminlee pushed a commit to escape-velocity-ventures/openclaw that referenced this pull request Mar 7, 2026
thebenjaminlee pushed a commit to escape-velocity-ventures/openclaw that referenced this pull request Mar 7, 2026
…k indicator (openclaw#26881)

The followup runner (used for queued messages, inter-agent sends,
heartbeat followups, etc.) only called typing.markRunComplete() in
its finally block.  The typing controller requires BOTH markRunComplete
AND markDispatchIdle to trigger cleanup — but markDispatchIdle was
only wired through the buffered dispatcher path, which followup turns
bypass entirely.

This caused the typing indicator to persist indefinitely on channels
like Telegram when the agent replied with NO_REPLY or produced empty
payloads, because the keepalive loop was never stopped.

Adds markDispatchIdle() alongside markRunComplete() in the followup
runner's finally block, and four test cases covering NO_REPLY, empty
payloads, agent errors, and successful delivery.

Complements openclaw#26295 which addressed the channel-level callback layer.

Fixes openclaw#26595

Co-authored-by: Samantha <samantha@Samanthas-Mac-mini.local>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: discord Channel integration: discord maintainer Maintainer-authored PR size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant